> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kameleoon.com/llms.txt
> Use this file to discover all available pages before exploring further.

# C++ SDK

> Integrate the Kameleoon C++ SDK to run experiments and activate feature flags in C++ services and web back-ends.

Integrating the SDK into your app is easy, and its footprint (memory and network usage) is low.

**Getting started**: For help getting started, see the [developer guide](#developer-guide).

**Latest version**: `0.8.5`. See the [C++ SDK changelog](https://github.com/Kameleoon-new/client-cpp/blob/main/CHANGELOG.md) for release notes, or the [SDK changelog index](/developer-docs/sdks/versions) for all SDKs.

**SDK methods**: For the full reference documentation of the C++ SDK, see the [reference](#reference) section.

## Developer guide

This guide is designed to help you integrate the C++ SDK quickly and start evaluating feature flags in your C++ app.

### Getting started

#### Install the C++ client

The C++ SDK is a C++17 library built on top of the Kameleoon SDK Core, a native shared library (`libkameleoon_ffi`) written in Rust that implements the SDK behavior. Your app links the C++ library statically and loads the core library at runtime.

<Warning>
  The Kameleoon SDK Core is compiled from source for your target platform and architecture. Make sure **Rust** and **Cargo** are installed and available in your system's `PATH`, and install `cbindgen`, which generates the C header of the core library:

  ```bash theme={null}
  cargo install cbindgen --locked
  ```

  Install Rust and Cargo: [https://rust-lang.org/tools/install](https://rust-lang.org/tools/install)

  Future releases will provide prebuilt core libraries for common platforms to simplify installation. Local compilation will remain available for users who prefer to build from source.
</Warning>

Requirements:

* A C++17 compiler (GCC, Clang, or MSVC) and CMake 3.16 or later.
* Rust 1.91 or later, Cargo, and `cbindgen` to build the SDK Core library (`libkameleoon_ffi.so` on Linux, `libkameleoon_ffi.dylib` on macOS, `kameleoon_ffi.dll` with its import library `kameleoon_ffi.dll.lib` on Windows).

Clone the [C++ SDK repository](https://github.com/Kameleoon-new/client-cpp) and build the core library, then build the C++ library against it:

```bash theme={null}
git clone https://github.com/Kameleoon-new/client-cpp.git
cd client-cpp

# Builds the SDK Core shared library into target/release
cargo build --locked -p kameleoon-ffi --release

# Builds the C++ library; cbindgen generates the core header during this step
cmake -S cpp/client -B cpp/client/build -DKAMELEOON_CPP_BUILD_TESTS=OFF -DKAMELEOON_CPP_BUILD_EXAMPLES=OFF
cmake --build cpp/client/build
```

To integrate the SDK into your own CMake project, add the `cpp/client` directory with `add_subdirectory()` and link the `Kameleoon::CppClient` target. Point `KAMELEOON_FFI_LIBRARY` to the core library you built; the C header is generated with `cbindgen`, or you can pass an existing one with `KAMELEOON_FFI_HEADER`:

```cmake title="CMakeLists.txt" theme={null}
set(KAMELEOON_FFI_LIBRARY "${CMAKE_SOURCE_DIR}/third_party/client-cpp/target/release/libkameleoon_ffi.so" CACHE FILEPATH "")
set(KAMELEOON_CPP_BUILD_TESTS OFF CACHE BOOL "")
set(KAMELEOON_CPP_BUILD_EXAMPLES OFF CACHE BOOL "")

add_subdirectory(third_party/client-cpp/cpp/client)

target_link_libraries(my_app PRIVATE Kameleoon::CppClient)
```

<Note>
  * `libkameleoon_ffi` is loaded at runtime: install it to a standard library location, or ship it next to your executable. On Linux and macOS, shipping it next to the executable requires an RPATH of `$ORIGIN` or `@loader_path` (for example, `set_target_properties(my_app PROPERTIES INSTALL_RPATH "$ORIGIN")`). On Windows, copy `kameleoon_ffi.dll` next to your binaries.
  * When you create a client, the SDK checks that the loaded `libkameleoon_ffi` matches the version it was built against and throws a `KameleoonException` with `ErrorCode::Internal` otherwise. Always upgrade the C++ library and the core library together.
  * All public SDK types live in the `kameleoon` namespace. Include `kameleoon/kameleoon.hpp` to get the whole public API.
</Note>

#### Additional configuration

Create a JSON configuration file (for example, `client-cpp.json`) to provide credentials and customize SDK behavior. You can also [download a sample configuration](/assets/developer-docs/sdks/web-sdks/client-configs/client-config.json) file.

The C++ SDK can be configured either with a JSON file whose path you pass to `KameleoonClientFactory::create()`, or by filling a `KameleoonClientConfig` struct directly in code.

The following table shows the available properties that you can set:

| Key (Code / Config File) | Description | Default value |
| - | - | - |
| `client_id` / `clientId` <Badge color="red" size="sm">required</Badge> | Required for authentication to the Kameleoon service. To find your `client_id`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | |
| `client_secret` / `clientSecret` <Badge color="red" size="sm">required</Badge> | Required for authentication to the Kameleoon service. To find your `client_secret`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | |
| `session_duration_minutes` / `sessionDurationMinutes` <Badge color="green" size="sm">optional</Badge> | Time interval, in minutes, during which the SDK keeps a visitor and their associated data in memory. | `30` minutes |
| `refresh_interval_minutes` / `refreshIntervalMinutes` <Badge color="green" size="sm">optional</Badge> | Interval, in minutes, used to refresh the active experiments and feature flags configuration. | `60` minutes |
| `default_timeout_millis` / `defaultTimeoutMillis` <Badge color="green" size="sm">optional</Badge> | Default timeout, in milliseconds, for SDK network requests. | `10000` milliseconds |
| `tracking_interval_millis` / `trackingIntervalMillis` <Badge color="green" size="sm">optional</Badge> | Interval, in milliseconds, used to batch tracking requests. Values are clamped to the `[1000, 5000]` range. | `1000` milliseconds |
| `environment` / `environment` <Badge color="green" size="sm">optional</Badge> | Environment from which the feature flag configuration should be used. The value can be `production`, `staging`, or `development`. | `std::nullopt` (treated as `production`) |
| `top_level_domain` / `topLevelDomain` <Badge color="green" size="sm">optional</Badge> | The current top-level domain for your website. Use the format `example.com` without protocol or subdomains. | `std::nullopt` |
| `proxy_host` / `proxyHost` <Badge color="green" size="sm">optional</Badge> | Proxy host for outgoing SDK calls. Supported formats: `https://my.prox`, `https://my.prox:4545`, `socks5://192.168.1.1:9000`. | `std::nullopt` |
| `network_domain` / `networkDomain` <Badge color="green" size="sm">optional</Badge> | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `std::nullopt` |

#### Initialize the Kameleoon client

After you have installed the SDK and configured your credentials, create a `KameleoonClient` by using `KameleoonClientFactory`.

<Tabs>
  <Tab title="Config File">
    ```cpp theme={null}
    #include "kameleoon/kameleoon.hpp"

    using namespace kameleoon;

    KameleoonClient create_client()
    {
        const std::string site_code = "a8st4f59bj";

        KameleoonClient client = KameleoonClientFactory::create(site_code, "/etc/kameleoon/client-cpp.json");
        client.initialize();

        return client;
    }
    ```
  </Tab>

  <Tab title="Code">
    ```cpp theme={null}
    #include "kameleoon/kameleoon.hpp"

    using namespace kameleoon;

    KameleoonClient create_client()
    {
        const std::string site_code = "a8st4f59bj";

        KameleoonClientConfig config{
            .client_id = "<client-id>",                  // mandatory
            .client_secret = "<client-secret>",          // mandatory
            .refresh_interval_minutes = 60,              // optional (60 minutes by default)
            .session_duration_minutes = 30,              // optional (30 minutes by default)
            .default_timeout_millis = 10'000,            // optional (10000 ms by default)
            .tracking_interval_millis = 1'000,           // optional (1000 ms by default)
            .proxy_host = "http://192.168.0.25:8080",    // optional
            .environment = "development",                // optional
            .top_level_domain = ".example.com",          // mandatory if you use hybrid mode (engine or web experiments)
            .network_domain = "example.com",             // optional
        };

        KameleoonClient client = KameleoonClientFactory::create(site_code, config);
        client.initialize();

        return client;
    }
    ```
  </Tab>
</Tabs>

<Note>
  The examples on this page use C++20 [designated initializers](https://en.cppreference.com/w/cpp/language/aggregate_initialization#Designated_initializers) (`GetVariationOptions{.track = false}`) to fill the SDK's configuration, options, and data structs. With a C++17 compiler, declare the struct and set its fields one by one instead.
</Note>

A `KameleoonClient` is the main object used to evaluate feature flags, add visitor data, and send tracking requests.

<Warning>
  * It's recommended to use `KameleoonClient` as a singleton object, as it serves as the bridge between your app and the Kameleoon platform. It exposes all required methods and properties to run experiments efficiently. `KameleoonClient` is move-only: keep it in one place and pass it by reference.
  * The C++ SDK initializes asynchronously. You should call [`initialize()`](#initialize) before relying on feature evaluation in production code.
  * Every SDK failure is reported as a [`KameleoonException`](#error-handling). Wrap SDK calls in `try`/`catch` blocks.
</Warning>

#### Activating a feature flag

##### Assigning a unique ID to a user

To assign a unique ID to a user, you can use the [`get_visitor_code()`](#get_visitor_code) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `default_visitor_code` that you would have generated. The ID is then set in a response headers cookie.

If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `get_visitor_code()` method ensures that the app file `engine.js` (previously named, `kameleoon.js`) and the SDK share the unique ID (**visitor code**).

##### Retrieving a flag configuration

To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account.

To determine the status or variation of a feature flag for a specific user, you should use the [`get_variation()`](#get_variation) or [`is_feature_active()`](#is_feature_active) method to retrieve the configuration based on the `feature_key`.

The `get_variation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `feature_key` and `visitor_code`.

You can use the `is_feature_active()` method to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options.

If your feature flag has associated variables (such as specific behaviors tied to each variation) `get_variation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the feature flag targets the user, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK sends the exposure event to the specified experiment on the next tracking request, which it triggers automatically based on the SDK’s [`tracking_interval_millis`](#additional-configuration). The default interval is 1000 milliseconds (1 second).

The `get_variation()` method allows you to control whether the SDK tracks the visitor. If `track=false`, the SDK sends no exposure events. This option is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `get_variations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics)

##### Adding data points to target a user or filter / breakdown visits in reports

To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`add_data()`](#add_data) method to add these data points to the user's profile.

To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`get_remote_visitor_data()`](#get_remote_visitor_data) method. This method asynchronously fetches data from the servers. It's important to call `get_remote_visitor_data()` *before* retrieving the variation or checking if the feature flag is active, because the SDK might need this data to assign a user to a given variation.

To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation).

Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience).

If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis.

<Note>
  To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type.
</Note>

##### Tracking goal conversions

When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`track_conversion()`](#track_conversion) method and provide the required `visitor_code` and `goal_id` parameters.

The SDK sends the conversion tracking request along with the next scheduled tracking request, which occurs at regular intervals (defined by [`tracking_interval_millis`](#additional-configuration)). If you prefer to send the request immediately, use the [`flush_instant()`](#flush) method.

##### Sending events to analytics solutions

To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`get_engine_tracking_code()`](#get_engine_tracking_code) method.

The `get_engine_tracking_code()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform.

### Error handling

Every error the SDK raises is a `KameleoonException`, so a single handler for that type catches every SDK failure. Handle a specific error type when your app needs to react to a particular failure, for example to fall back to your default behavior when a feature flag isn't in the configuration or the SDK isn't initialized yet. The reference section of each method lists the errors it can raise.

`KameleoonException` derives from `std::runtime_error`. Call `code()` to get the `ErrorCode` that tells the errors apart, and `what()` to get the message.

```cpp theme={null}
#include "kameleoon/kameleoon.hpp"

#include <iostream>

using namespace kameleoon;

try {
    Variation variation = client.get_variation(visitor_code, "new_checkout");
} catch (const KameleoonException& error) {
    switch (error.code()) {
    case ErrorCode::FeatureNotFound:
        // The feature flag isn't in the SDK configuration.
        break;
    case ErrorCode::Initialization:
        // The SDK isn't ready yet; fall back to your default behavior.
        break;
    default:
        std::cerr << "Kameleoon error: " << error.what() << "\n";
        break;
    }
}
```

| Error code | Description |
| - | - |
| `ErrorCode::Initialization` | The client couldn't be created (empty site code, missing or unreadable configuration, empty credentials), the SDK isn't initialized yet, or no initialization result was available before the `initialize()` timeout expired. |
| `ErrorCode::FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). |
| `ErrorCode::FeatureExperimentNotFound` | Exception indicating that the requested experiment id isn't in the SDK's internal configuration. This behavior is usually normal and means that the rule's corresponding experiment isn't yet active on Kameleoon's side. |
| `ErrorCode::FeatureVariationNotFound` | Exception indicating that the requested variation key(id) isn't in the internal configuration of the SDK. This behavior is usually normal and means that the variation's corresponding experiment isn't yet active on Kameleoon's side. |
| `ErrorCode::FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). |
| `ErrorCode::FeatureRuleNotFound` | The requested rule wasn't found in the SDK configuration. |
| `ErrorCode::FeatureEvaluationBlocked` | Exception indicating that feature evaluation is blocked. The reason is described in the error message. This usually occurs due to GDPR restrictions when the visitor hasn't provided legal consent. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |
| `ErrorCode::InvalidSimVarCookieFormat` | Indicates that the `kameleoonSimulationFFData` cookie contains malformed JSON and the simulated variations couldn't be parsed. |
| `ErrorCode::InvalidArgument` | An argument is invalid: a string isn't valid UTF-8, an enum value is unknown, a timeout is zero or negative, or an input exceeds the supported length. |
| `ErrorCode::Network` | A network request failed or the server responded with a non-success status code: the configuration fetch, a remote data or remote visitor data request, or a warehouse audience request. Also returned when the client can't be created because `proxy_host` is invalid. |
| `ErrorCode::Internal` | An unexpected internal SDK error, for example a `libkameleoon_ffi` version mismatch, a method called on a moved-from client, or a variation key that is missing from the configuration. |

<Note>
  Strings passed to the SDK must be valid UTF-8. Callbacks you provide to the SDK (`CookieAccessor` overrides, event handlers, and loggers) must not throw: any exception that escapes them is caught at the SDK boundary and discarded.
</Note>

<Note>
  SDK errors are expected behavior, not crashes: a feature flag that isn't activated yet, an SDK that is still loading its configuration, or a network failure are all normal conditions. When feature evaluation fails, log the error and fall back to your default behavior so the visitor still gets a working experience.
</Note>

### Asynchronous operations

The network-bound operations ([`initialize()`](#initialize), [`flush_instant()`](#flush), [`get_remote_data()`](#get_remote_data), [`get_remote_visitor_data()`](#get_remote_visitor_data), and [`get_visitor_warehouse_audience()`](#get_visitor_warehouse_audience)) run on the SDK's own worker thread. Each comes in three forms, so you can pick the one that fits the calling thread:

| Form | Example | Use when |
| - | - | - |
| Blocking | `client.initialize(timeout)` | The thread can wait: startup, shutdown, batch jobs, tests. |
| Completion | `client.initialize_async(timeout, on_done)` | You run an event loop (C++17). The call returns immediately and `on_done` receives the outcome. |
| Awaitable | `co_await client.co_initialize(timeout)` | You write C++20 coroutines. |

```cpp theme={null}
// Blocking: waits until the data is fetched, throws a KameleoonException on failure.
client.get_remote_visitor_data(visitor_code);

// Completion: returns immediately; `error` is null on success.
client.get_remote_visitor_data_async(visitor_code, {}, [](std::exception_ptr error) {
    if (error) {
        // Rethrow `error` to get the KameleoonException.
        return;
    }
    // The remote data is now available locally.
});

// C++20 coroutine: throws a KameleoonException on failure.
co_await client.co_get_remote_visitor_data(visitor_code);
```

A completion is a `Completion<void>` (`std::function<void(std::exception_ptr)>`), or a `Completion<T>` (`std::function<void(std::exception_ptr, T)>`) for operations that return a value. The SDK invokes it exactly once. You can omit the completion to run the operation fire-and-forget: failures then only appear in the SDK log.

<Warning>
  Completions and event handlers usually run on the SDK worker thread, but a completion can also run on the calling thread, before the `*_async` call returns: for example, when the SDK fails fast (the client isn't ready, the visitor code is invalid) or when `initialize_async()` is called on a client that is already initialized. Don't assume a completion runs on a particular thread or after the call returns, and don't hold a lock across the call that the completion also takes. Completions and event handlers must not block, must not throw, and must not call the blocking form of an SDK operation: it waits for the same worker thread and deadlocks. Hand the outcome to your own event loop and finish the work there.
</Warning>

<Note>
  C++20 has no built-in coroutine task type, so `co_await` only works inside a coroutine whose promise type accepts the SDK's `Awaitable<T>` (`KAMELEOON_HAS_COROUTINES` is defined by `kameleoon/async.hpp` when coroutines are available). By default the coroutine resumes on the SDK worker thread; call `.via(executor)` on the awaitable to resume on your own event loop instead. Frameworks with their own coroutine types, such as Boost.Asio, need a small adapter around the completion form; see the server example shipped with the SDK.
</Note>

The evaluation methods (`get_visitor_code()`, `get_variation()`, `get_variations()`, `is_feature_active()`, `add_data()`, `track_conversion()`, and `flush()`) are synchronous in-memory calls and need none of this.

### Using a custom bucketing key

By default, Kameleoon uses a unique, anonymous visitor ID (`visitor_code`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs, and in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag.

The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitor_code`.

#### Use cases

Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations:

* **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `account_id`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company.

By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, which leads to more reliable results and a better user experience.

#### Technical details

When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data:

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {CustomData(42, {"new_visitor_code"})});
```

* **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`add_data()`](#add_data) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `new_visitor_code` refers to the identifier you wish to use for your bucketing (for example, the new `user_id` or `account_id`).

<Warning>
  For the custom bucketing key to function correctly, you must also define and configure it for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#advanced-flag-settings).
</Warning>

* **Bucketing logic:** Once you provide a custom bucketing key through the `add_data()` method, all hash calculations for assigning users to variations use this `new_visitor_code` (your custom key) instead of the default `visitor_code`. Using the `new_visitor_code` means that the bucketing decision depends on your custom identifier, ensuring consistent assignments across various contexts where that identifier is present.
* **Data tracking and analytics:** It's crucial to note that while the `new_visitor_code` (your custom key) is used for bucketing decisions, the SDK sends **all subsequent data (tracking events and conversions, for example) and associates it with the *original* `visitor_code`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when you bucket at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting.

#### Technical requirements

To effectively use a custom bucketing key:

* The key must be a `std::string`.
* It must be unique for the entity you intend to bucket (for example, if using a `user_id`, each user's ID should be unique).
* The key must be available to the SDK at the exact moment it evaluates the feature flag decision for that user or request.

### Targeting conditions

The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation).

You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users).

### Cross-device experimentation

To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation).

#### Synchronizing custom data across devices

Although custom mapping synchronization aligns visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required:

**Same user ID across devices**
If you use the same user ID consistently across all devices, the SDK handles synchronization automatically without a custom mapping sync. It's enough to call the `get_remote_visitor_data()` method when you want to sync the data collected between multiple devices.

**Multi-server instances with consistent IDs**
In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `get_remote_visitor_data()`) is sufficient without additional custom mapping sync.

Customers who need additional data can refer to the [`get_remote_visitor_data()`](#get_remote_visitor_data) method description for further guidance. In the below code, it's assumed that you use the same unique identifier (in this case, the `visitor_code`, also known as `userId`) consistently between the two devices for accurate data retrieval.

<Note>
  If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data.
</Note>

```cpp title="Device A" theme={null}
// In this example, a Custom data with index `90` was set to "Visitor" scope in Kameleoon.
using namespace kameleoon;

constexpr uint32_t VISITOR_SCOPE_CUSTOM_DATA_INDEX = 90;

client.add_data(visitor_code, {CustomData(VISITOR_SCOPE_CUSTOM_DATA_INDEX, {"your data"})});

client.flush_instant(visitor_code);
```

```cpp title="Device B" theme={null}
// Before working with the data, call the `get_remote_visitor_data` method.
client.get_remote_visitor_data(visitor_code);

// After calling the method, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A.
// So, "your data" will be available for targeting and tracking the visitor.
```

#### Using custom data for session merging

[Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation).

After you enable cross-device reconciliation, calling [`get_remote_visitor_data()`](#get_remote_visitor_data) with the parameter `userId` retrieves all known data for a given user.

Sessions with the same identifier always see the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor.

The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. The [cross-device experimentation documentation](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights) outlines these limitations.

Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform.

Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging:

* `get_remote_visitor_data()` with added `UniqueIdentifier(true)` - to retrieve data for all linked visitors.
* [`track_conversion()`](#track_conversion) or [`flush()`](#flush) with added `UniqueIdentifier(true)` data - to track some data for specific visitor that's associated with another visitor.

<Tip>
  Because the custom data you use as the identifier requires **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`get_remote_visitor_data()`](#get_remote_visitor_data) method on each device.
</Tip>

Here's an example of how to use custom data for session merging.

```cpp theme={null}
// In this example, 91 represents the Custom Data's index configured as a unique identifier in Kameleoon.
using namespace kameleoon;

constexpr uint32_t MAPPING_INDEX = 91;
const std::string FEATURE_KEY = "ff123";

const std::string anonymous_visitor_code = "anonymous-visitor";
const std::string user_id = "authenticated-user";

// 1. Before the visitor is authenticated

// Retrieve the variation for an unauthenticated visitor.
// Assume anonymousVisitorCode is the randomly generated ID for that visitor.
Variation anonymous_variation = client.get_variation(anonymous_visitor_code, FEATURE_KEY);

// 2. After the visitor is authenticated

// Assume `userId` is the visitor code of the authenticated visitor.
client.add_data(anonymous_visitor_code, {CustomData(MAPPING_INDEX, {user_id})});
client.flush_instant(anonymous_visitor_code);

// Indicate that `userId` is a unique identifier.
client.add_data(user_id, {UniqueIdentifier{true}});

// 3. After the visitor was authorized

// Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation.
Variation user_variation = client.get_variation(user_id, FEATURE_KEY);
bool is_same_variation = user_variation.key == anonymous_variation.key; // true

// `userId` and `anonymousVisitorCode` are now linked and can be tracked as a single visitor.
client.track_conversion(user_id, 123);

// Additionally, the linked visitors share all fetched previously tracked remote data.
client.get_remote_visitor_data(user_id);
```

In this example, the app has a login page. Since the user ID is unknown at the moment of login, the app uses an anonymous visitor identifier generated by the [`get_visitor_code()`](#get_visitor_code) method. After the user logs in, the app associates the anonymous visitor with the user ID and uses it as a unique identifier for the visitor.

### Logging

The SDK generates logs to reflect various internal processes and issues.

#### Log levels

The SDK supports configuring limiting logging by a log level.

```cpp theme={null}
#include "kameleoon/logging.hpp"

using namespace kameleoon;

// The `None` log level does not allow logging.
KameleoonLogger::set_log_level(LogLevel::None);

// The `Error` log level only allows logging issues that may affect the SDK's primary behaviour.
KameleoonLogger::set_log_level(LogLevel::Error);

// The `Warning` log level allows logging issues which may require attention.
// It extends the `Error` log level.
// The `Warning` log level is the default log level.
KameleoonLogger::set_log_level(LogLevel::Warning);

// The `Info` log level allows logging general information on the SDK's internal processes.
// It extends the `Warning` log level.
KameleoonLogger::set_log_level(LogLevel::Info);

// The `Debug` level logs additional details about the SDK's internal processes and extends the `Info` level
// with more granular diagnostic output.
// This information is not intended for end-user interpretation but can be sent to our support team
// to assist with internal troubleshooting.
KameleoonLogger::set_log_level(LogLevel::Debug);
```

#### Custom handling of logs

The SDK writes its logs to the console output by default. This behaviour can be overridden.

<Note>
  The SDK limits logging by log level separately from the log handling logic.
</Note>

```cpp theme={null}
#include "kameleoon/logging.hpp"

#include <spdlog/spdlog.h>

using namespace kameleoon;

// Log level filtering is applied separately from log handling logic.
// The custom logger will only receive logs that meet or exceed the specified log level.
// Ensure the log level is set correctly.
KameleoonLogger::set_log_level(LogLevel::Debug); // Optional; defaults to `LogLevel::Warning`.

// The logger is any callable accepting the log level and the message.
// It may be invoked from SDK threads, so it must be thread-safe and must not throw.
KameleoonLogger::set_logger([](LogLevel level, const std::string& message) {
    switch (level) {
    case LogLevel::Error:   spdlog::error(message); break;
    case LogLevel::Warning: spdlog::warn(message); break;
    case LogLevel::Info:    spdlog::info(message); break;
    case LogLevel::Debug:   spdlog::debug(message); break;
    case LogLevel::None:    break;
    }
});

// Passing an empty logger restores the default logger of the SDK, which writes to stdout.
KameleoonLogger::set_logger({});
```

## Reference

This is the full reference documentation for the C++ SDK.

### Initialization

#### create()

To use the SDK, create a `KameleoonClient` with `KameleoonClientFactory::create()`, either from a `KameleoonClientConfig` struct or from a JSON configuration file.

Clients are cached per site code and environment: repeated `create()` calls with the same site code and environment return clients over the same underlying SDK instance, and the configuration of the first call wins.

<Tabs>
  <Tab title="create(config struct)">
    ```cpp theme={null}
    #include "kameleoon/kameleoon.hpp"

    using namespace kameleoon;

    KameleoonClientConfig config{
        .client_id = "<client-id>",                  // mandatory
        .client_secret = "<client-secret>",          // mandatory
        .refresh_interval_minutes = 60,              // optional (60 minutes by default)
        .session_duration_minutes = 30,              // optional (30 minutes by default)
        .default_timeout_millis = 10'000,            // optional (10000 ms by default)
        .tracking_interval_millis = 1'000,           // optional (1000 ms by default)
        .proxy_host = "http://192.168.0.25:8080",    // optional
        .environment = "development",                // optional
        .top_level_domain = ".example.com",          // mandatory if you use hybrid mode (engine or web experiments)
        .network_domain = "example.com",             // optional
    };

    KameleoonClient client = KameleoonClientFactory::create(site_code, config);
    ```

    ##### Parameters

    | Name | Type | Description |
    | - | - | - |
    | `site_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique key of the Kameleoon project used by the SDK. |
    | `config` <Badge color="red" size="sm">required</Badge> | `KameleoonClientConfig` | SDK configuration struct. |
  </Tab>

  <Tab title="create(config file)">
    ```cpp theme={null}
    #include "kameleoon/kameleoon.hpp"

    using namespace kameleoon;

    KameleoonClient client = KameleoonClientFactory::create("a8st4f59bj", "/etc/kameleoon/client-cpp.json");
    ```

    ##### Parameters

    | Name | Type | Description |
    | - | - | - |
    | `site_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique key of the Kameleoon project used by the SDK. |
    | `config_path` <Badge color="red" size="sm">required</Badge> | `std::string` | Path to the JSON configuration file. |
  </Tab>
</Tabs>

##### Return value

| Type | Description |
| - | - |
| `KameleoonClient` | A client instance. The client isn't ready to evaluate feature flags until [`initialize()`](#initialize) has completed. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | The site code is empty, `client_id` or `client_secret` is empty, or the configuration file can't be read or parsed. The error message names the exact cause. |
| `ErrorCode::Internal` | The loaded `libkameleoon_ffi` library doesn't match the version the SDK was built against. |

#### initialize()

Use `initialize()` when your app should wait for the Kameleoon client to finish initialization before it evaluates feature flags.

The call returns the result of the configuration fetch: it completes successfully when the client finishes initializing, and fails if the initial configuration fetch fails before the client becomes ready. Background retries continue after that first failure. Once a retry succeeds, subsequent calls return a successful result. If no initialization result is available before the timeout expires, the call fails. If you don't provide a `timeout` value, the SDK uses the default timeout from [`default_timeout_millis`](#additional-configuration).

<Tip>
  Call `initialize()` once on app startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`is_ready()`](#is_ready) as a non-blocking guard before evaluating feature flags. Even if `initialize()` fails, the SDK keeps retrying the configuration fetch in the background, so `is_ready()` starts returning `true` after a retry succeeds.
</Tip>

```cpp theme={null}
#include <chrono>

// Initializes the client using the configured default timeout (blocking).
client.initialize();

// Initializes the client with a custom timeout of 5 seconds (blocking).
client.initialize(std::chrono::seconds(5));

// Initializes the client without blocking; the completion receives the outcome.
client.initialize_async(std::chrono::seconds(5), [](std::exception_ptr error) {
    if (error) {
        // Initialization failed: rethrow `error` to get the `KameleoonException`.
        return;
    }
    // The client is ready.
});

// C++20 coroutines
co_await client.co_initialize(std::chrono::seconds(5));
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `timeout` <Badge color="green" size="sm">optional</Badge> | `std::optional<std::chrono::milliseconds>` | The maximum time to wait for an initialization result. | `default_timeout_millis` |

##### Return value

| Type | Description |
| - | - |
| `void` | Returns once the SDK is ready. Throws a `KameleoonException` if the initial configuration fetch fails or no initialization result is available before the timeout expires. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | No initialization result was available before the timeout expired. |
| `ErrorCode::Network` | The initial fetch of the configuration failed because of a network error or an unexpected HTTP status. The SDK keeps retrying in the background, so a later call succeeds once a retry does. |
| `ErrorCode::InvalidArgument` | The provided `timeout` is zero or negative. |

#### is\_ready()

`is_ready()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`initialize()`](#initialize), this method returns immediately without blocking or throwing.

<Tip>
  * Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state.
  * Call `initialize()` once on app startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`is_ready()`](#is_ready) as a non-blocking guard before evaluating feature flags. Even if `initialize()` fails, the SDK keeps retrying the configuration fetch in the background, so `is_ready()` starts returning `true` after a retry succeeds.
</Tip>

```cpp theme={null}
if (client.is_ready()) {
    // The client is ready
}
```

##### Return value

| Type | Description |
| - | - |
| `bool` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). |

#### forget()

Removes the cached SDK client associated with the specified `site_code`, so the next `create()` call with the same site code and environment creates a new one.

`forget()` only drops the factory's reference. Every `KameleoonClient` created for that site code keeps the underlying SDK instance (and its worker thread, polling, and tracking) alive until it's destroyed, and outstanding asynchronous operations keep it alive until their completions finish. To release the background resources, call `forget()` and destroy all `KameleoonClient` instances of that site code.

```cpp theme={null}
using namespace kameleoon;

// Removes the cached client created for the given site code without an environment
KameleoonClientFactory::forget("a8st4f59bj");

// Removes the cached client for the given site code and environment
KameleoonClientFactory::forget("a8st4f59bj", "production");
```

##### Parameters

| Name | Type | Description |
| - | - | - |
| `site_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the Kameleoon project. |
| `environment` <Badge color="green" size="sm">optional</Badge> | `std::string` | Environment the client was created with. A client created with an `environment` is only removed when the same value is passed here. |

### Feature flags and variations

#### is\_feature\_active()

* 📨 *Sends tracking data to Kameleoon (depending on the `track` option)*

Determines whether a feature flag is active for a given user.

If the visitor hasn't yet been evaluated for this feature flag, the SDK evaluates the targeting rules and returns the result. If the visitor already has a stored evaluation for the feature, the SDK reuses the existing result to ensure consistency.

<Note>
  Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `is_feature_active()`, `get_variation()` or `get_variations()`.

  Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors.

  For example, if you call `get_variations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor.

  Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session.
</Note>

<Warning>
  The `is_feature_active()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**.
</Warning>

```cpp theme={null}
using namespace kameleoon;

const std::string feature_key = "new_checkout";

// Evaluates the feature flag and sends tracking data (default behavior)
bool active = client.is_feature_active(visitor_code, feature_key);

// Evaluates the feature flag without sending tracking data
bool active_without_tracking = client.is_feature_active(
    visitor_code, feature_key, IsFeatureActiveOptions{.track = false});
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the user. | |
| `feature_key` <Badge color="red" size="sm">required</Badge> | `std::string` | Key of the feature to evaluate for the user. | |
| `options` <Badge color="green" size="sm">optional</Badge> | `IsFeatureActiveOptions` | Options struct with a single `track` field (`bool`) that enables or disables tracking of the feature evaluation. | `track = true` |

##### Return value

| Type | Description |
| - | - |
| `bool` | Indicates whether the feature flag is active for the specified `visitor_code`. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |

#### get\_variation()

* 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)*

Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag.

This method takes a `visitor_code` and `feature_key` as mandatory arguments. The `track` argument is optional and defaults to `true`.

It returns the assigned `Variation` for the visitor. If the visitor isn't associated with any feature flag rules, the method returns the default `Variation` for the given feature flag.

Ensure your code includes proper error handling to manage potential exceptions.

<Note>
  The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface.
</Note>

```cpp theme={null}
using namespace kameleoon;

const std::string feature_key = "new_checkout";

// Retrieves the variation assigned to the visitor (with tracking enabled by default)
Variation variation = client.get_variation(visitor_code, feature_key);

// Retrieves the variation without sending tracking data
Variation variation_without_tracking = client.get_variation(
    visitor_code, feature_key, GetVariationOptions{.track = false});
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the visitor. | |
| `feature_key` <Badge color="red" size="sm">required</Badge> | `std::string` | Key of the feature you want to expose to a visitor. | |
| `track` <Badge color="green" size="sm">optional</Badge> | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` |

<Note>
  The optional `track` parameter is passed as the `track` field of a `GetVariationOptions` struct.
</Note>

##### Return value

| Type | Description |
| - | - |
| `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |
| `ErrorCode::FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). |
| `ErrorCode::FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). |
| `ErrorCode::FeatureEvaluationBlocked` | Exception indicating that feature evaluation is blocked. The reason is described in the error message. This usually occurs due to GDPR restrictions when the visitor hasn't provided legal consent. |

#### get\_variations()

* 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)*

Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags.

This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitor_code` as a mandatory argument, while `only_active` and `track` are optional.

* If you set `only_active` to `true`, the method `get_variations()` returns feature flag variations only if the visitor isn't bucketed with the `off` variation.
* The `track` parameter controls whether the method tracks the variation assignments. By default, it's `true`. If it's `false`, the method doesn't track variation assignments.

The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If the SDK assigns no variation for a feature flag, the method returns the default `Variation` for that flag.

Implement proper error handling to manage potential exceptions.

<Note>
  The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface.
</Note>

```cpp theme={null}
using namespace kameleoon;

// Retrieves all variations assigned to the visitor (with default options)
std::unordered_map<std::string, Variation> variations = client.get_variations(visitor_code);

// Retrieves only active variations for the visitor
auto only_active_variations = client.get_variations(
    visitor_code, GetVariationsOptions{.only_active = true});

// Retrieves variations without sending tracking data
auto variations_without_tracking = client.get_variations(
    visitor_code, GetVariationsOptions{.track = false});
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the visitor. | |
| `only_active` <Badge color="green" size="sm">optional</Badge> | `bool` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` |
| `track` <Badge color="green" size="sm">optional</Badge> | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` |

<Note>
  The optional `only_active` and `track` parameters are passed as the fields of a `GetVariationsOptions` struct.
</Note>

##### Return value

| Type | Description |
| - | - |
| `std::unordered_map<std::string, Variation>` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |

#### set\_forced\_variation()

The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. The method is especially valuable for controlled experiments where the usual evaluation logic isn't required or you need to skip it. It can also be helpful in scenarios like debugging or custom testing.

When you set a **forced** variation, it overrides Kameleoon's real-time evaluation logic. The SDK skips processes like segmentation, targeting conditions, and algorithmic calculations. To preserve segmentation and targeting conditions during an experiment, set `force_targeting=false` instead.

<Info>
  **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation starts, the SDK fully processes and completes it first.
</Info>

Kameleoon treats a forced variation the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting.

The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient.

<Warning>
  It’s important to distinguish **forced** variations from **[simulated](#get_visitor_code)** variations:

  * **Forced variations**: Are specific to an individual experiment.
  * **Simulated variations**: Affect the overall **feature flag** result.
</Warning>

```cpp theme={null}
using namespace kameleoon;

const uint32_t experiment_id = 202387;

// Forces the visitor into "variation_2" for the given experiment
client.set_forced_variation(visitor_code, experiment_id, "variation_2");

// Removes any previously forced variation for the visitor in this experiment
client.set_forced_variation(visitor_code, experiment_id, std::nullopt);

// Forces the visitor into "variation_2" with custom options
// In this case, targeting rules are respected (force_targeting = false)
client.set_forced_variation(
    visitor_code, experiment_id, "variation_2", SetForcedVariationOptions{.force_targeting = false});
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the visitor. | |
| `experiment_id` <Badge color="red" size="sm">required</Badge> | `uint32_t` | **Experiment Id** that the SDK targets and selects during the evaluation process. | |
| `variation_key` <Badge color="red" size="sm">required</Badge> | `std::optional<std::string>` | **Variation Key** corresponding to a `Variation` that the SDK forces as the returned value for the experiment. If the value is `std::nullopt`, the forced variation will be reset. | |
| `force_targeting` <Badge color="green" size="sm">optional</Badge> | `bool` | Indicates whether the SDK forces and skips targeting for the experiment (`true`) or applies it as in the standard evaluation process (`false`). | `true` |

<Note>
  The optional `force_targeting` parameter is passed as the `force_targeting` field of a `SetForcedVariationOptions` struct.
</Note>

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |
| `ErrorCode::FeatureExperimentNotFound` | Exception indicating that the requested experiment id isn't in the SDK's internal configuration. This behavior is usually normal and means that the rule's corresponding experiment isn't yet active on Kameleoon's side. |
| `ErrorCode::FeatureVariationNotFound` | Exception indicating that the requested variation key(id) isn't in the internal configuration of the SDK. This behavior is usually normal and means that the variation's corresponding experiment isn't yet active on Kameleoon's side. |

#### evaluate\_audiences()

* 📨 *Sends Tracking Data to Kameleoon*

This method evaluates visitors against all available Audiences Explorer segments and tracks those who match.

Call `evaluate_audiences()` **after you set or update all relevant visitor data**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that Kameleoon evaluates the visitor against the most current data available, allowing for accurate audience assignment based on all criteria.

After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer.

```cpp theme={null}
client.evaluate_audiences(visitor_code);
```

##### Parameters

| Name | Type | Description |
| - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the visitor. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |

<Info>
  In most cases, you only need to handle the basic error, `KameleoonException`, as the example demonstrates. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, you can handle general language errors by including `std::exception`.
</Info>

#### get\_datafile()

<Tip>
  To evaluate all feature flags, use [`get_variations()`](#get_variations). This method is more efficient than calling `DataFile` and iterating through flags with [`get_variation()`](#get_variation).
</Tip>

Returns the current SDK configuration as a [`DataFile`](#datafile) object.

The returned `DataFile` is a copy of the current configuration: it doesn't change when the SDK refreshes its configuration.

```cpp theme={null}
using namespace kameleoon;

DataFile datafile = client.get_datafile();
```

##### Return value

| Type | Description |
| - | - |
| `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |

### Visitor data

#### get\_visitor\_code()

Use `get_visitor_code()` to obtain the current visitor's Kameleoon `visitor_code`. The method works with any cookie store that implements the `CookieAccessor` interface.

The implementation logic is as follows:

1. The SDK checks whether a `kameleoonVisitorCode` cookie is already available through the provided accessor.
2. If the cookie is absent, the SDK uses `default_visitor_code` when you provide one.
3. Otherwise, the SDK generates a new visitor code and stores it through the accessor.

For more information, refer to [Hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/).

<Warning>
  If you provide your own `visitor_code`, its uniqueness must be guaranteed on your side. Also note that the length of `visitor_code` is limited to `255` characters.
</Warning>

<Info>
  The `get_visitor_code()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the method reads the cookie and stores the simulated variations for that visitor; its return value doesn't change. Subsequent feature evaluations for the visitor, such as [`get_variation()`](#get_variation) and [`is_feature_active()`](#is_feature_active), then bypass the standard evaluation process and return the simulated [`Variation`](#variation) based on the provided data.

  You can apply simulations in two ways:

  * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), Kameleoon creates the cookie automatically when you simulate a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode).
  * **Manually:** Set the `kameleoonSimulationFFData` cookie manually.

  It’s important to distinguish **simulated** variations from **[forced](#set_forced_variation)** variations:

  * **Simulated variations**: Affect the overall **feature flag** result.
  * **Forced variations**: Are specific to an individual experiment.

  ⚙️ **Manual setup**

  Ensure the `kameleoonSimulationFFData` cookie follows this format:

  * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`.
  * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`.

  ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent).
</Info>

##### `CookieAccessor` interface

Implement `kameleoon::CookieAccessor` to bridge SDK cookie reads and writes to your HTTP framework:

| Method | Description |
| - | - |
| `std::optional<std::string> get(const std::string& key)` | Returns the cookie value for `key`, or `std::nullopt` when the cookie is absent. |
| `void set(const std::string& key, const std::string& value, uint32_t max_age, std::optional<std::string> top_level_domain)` | Stores the cookie on the response, with the `max_age` (in seconds) and, when present, the `top_level_domain` as the cookie domain. |

<Note>
  * `get()` must return a cookie already written through `set()` on the response in preference to the request cookie of the same name. The SDK keeps no cookie state between calls, so this is what makes a second `get_visitor_code()` call within the same request return the code written by the first one instead of generating a new one.
  * The overrides are invoked from inside the SDK and must not throw. An exception escaping `get()` is treated as an absent cookie. Cookie values must be valid UTF-8.
</Note>

<Tabs>
  <Tab title="Custom">
    ```cpp theme={null}
    #include "kameleoon/kameleoon.hpp"

    #include <optional>
    #include <string>
    #include <unordered_map>

    using namespace kameleoon;

    // A cookie accessor over the cookies of an HTTP request. Cookies written by the
    // SDK are collected in `response_` and must be emitted as `Set-Cookie` headers.
    class RequestCookies final : public CookieAccessor {
    public:
        explicit RequestCookies(std::unordered_map<std::string, std::string> request)
            : request_(std::move(request)) {}

        void set(const std::string& key, const std::string& value, uint32_t max_age,
                 std::optional<std::string> top_level_domain) override {
            response_[key] = value;
            // Emit a `Set-Cookie` header with `max_age` and `top_level_domain` here.
        }

        std::optional<std::string> get(const std::string& key) override {
            // Prefer a cookie already written to the response in this request.
            if (const auto it = response_.find(key); it != response_.end()) return it->second;
            if (const auto it = request_.find(key); it != request_.end()) return it->second;
            return std::nullopt;
        }

    private:
        std::unordered_map<std::string, std::string> request_;
        std::unordered_map<std::string, std::string> response_;
    };

    RequestCookies cookies{parse_request_cookies(request)};

    // Generate or retrieve a visitor code using an auto-generated value
    std::string visitor_code = client.get_visitor_code(cookies);
    // Generate or retrieve a visitor code using a predefined user ID
    std::string visitor_code_with_default = client.get_visitor_code(cookies, "user_id");
    ```
  </Tab>

  <Tab title="Drogon">
    ```cpp theme={null}
    #include "kameleoon/kameleoon.hpp"

    #include <drogon/drogon.h>

    #include <map>
    #include <optional>
    #include <string>

    using namespace kameleoon;

    // Reads cookies from the request Drogon already parsed and collects the
    // SDK's cookies for the response.
    class DrogonCookieAccessor final : public CookieAccessor {
    public:
        explicit DrogonCookieAccessor(const drogon::HttpRequestPtr& request) : request_(request) {}

        void set(const std::string& key, const std::string& value, uint32_t max_age,
                 std::optional<std::string> top_level_domain) override {
            drogon::Cookie cookie{key, value};
            cookie.setPath("/");
            cookie.setMaxAge(static_cast<int>(max_age));
            if (top_level_domain.has_value() && !top_level_domain->empty()) {
                cookie.setDomain(std::move(*top_level_domain));
            }
            // Keyed by name: setting the same cookie twice yields one header.
            response_[key] = std::move(cookie);
        }

        std::optional<std::string> get(const std::string& key) override {
            // Check response cookies first, then fall back to request cookies
            if (const auto it = response_.find(key); it != response_.end()) return it->second.value();
            const auto& request_cookies = request_->getCookies();
            if (const auto it = request_cookies.find(key); it != request_cookies.end()) return it->second;
            return std::nullopt;
        }

        // Attach the SDK cookies to the response
        void apply(const drogon::HttpResponsePtr& response) const {
            for (const auto& [name, cookie] : response_) response->addCookie(cookie);
        }

    private:
        drogon::HttpRequestPtr request_;
        std::map<std::string, drogon::Cookie> response_;
    };

    void get_visitor_code(const drogon::HttpRequestPtr& request,
                          std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
        DrogonCookieAccessor cookies{request};

        // Retrieve or generate a visitor code using the SDK
        std::string visitor_code = client.get_visitor_code(cookies);

        auto response = drogon::HttpResponse::newHttpResponse();
        response->setBody(visitor_code);
        // Attach any new/updated cookies to the response headers
        cookies.apply(response);
        callback(response);
    }
    ```
  </Tab>
</Tabs>

##### Parameters

| Name | Type | Description |
| - | - | - |
| `cookies` <Badge color="red" size="sm">required</Badge> | `CookieAccessor&` | Cookie accessor used to read and store the visitor cookie. |
| `default_visitor_code` <Badge color="green" size="sm">optional</Badge> | `std::optional<std::string>` | Visitor code to use when no cookie is present. |

##### Return value

| Type | Description |
| - | - |
| `std::string` | String representing a unique visitor code used in SDK. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |
| `ErrorCode::InvalidSimVarCookieFormat` | Indicates that the `kameleoonSimulationFFData` cookie contains malformed JSON and the simulated variations couldn't be parsed. |

#### add\_data()

The `add_data()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor.

The `add_data()` method doesn't return any value and doesn't interact with Kameleoon backend servers on its own. Instead, the SDK saves all the declared data for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`.

The [`track_conversion()`](#track_conversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`get_variation()`](#get_variation) and [`get_variations()`](#get_variations) methods if an experimentation rule is triggered.

<Tip>
  Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index.
</Tip>

The `data` parameter is a `std::vector<Data>`, where `Data` is a `std::variant` of all the [data types](#data-types). Each data type converts implicitly to `Data`, so you can pass a braced list of data objects.

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {Browser{.type = BrowserType::Chrome, .version = 123.0F}});

client.add_data(visitor_code, {
    PageView{.url = "https://example.com/pricing", .title = "Pricing", .referrers = {3}},
    UserAgent{"Mozilla/5.0"},
});

// Stores the data locally for targeting only, without sending it to Kameleoon
client.add_data(visitor_code, {PageView{.url = "https://example.com/checkout", .title = "Checkout"}}, false);
```

##### Parameters

| Name | Type | Description | Default value |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the visitor. | |
| `data` <Badge color="red" size="sm">required</Badge> | `const std::vector<Data>&` | Collection of Kameleoon data types. | |
| `track` <Badge color="green" size="sm">optional</Badge> | `bool` | Specifies whether the added data is eligible for tracking. When set to `false`, the SDK stores the data locally, uses it only for targeting evaluation, and doesn't send it to the Kameleoon Data API. | `true` |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |
| `ErrorCode::InvalidArgument` | A string isn't valid UTF-8 or an enum value is unknown. |

#### flush()

* *📨 Sends tracking data to Kameleoon*

The `flush()` method aggregates all Kameleoon data associated with a visitor and sends a tracking request to the server. This request includes any data previously added via the [`add_data`](#add_data) method that hasn't yet been transmitted through other tracking mechanisms (see the referenced methods for details). The `flush()` operation is non-blocking, as the server call is performed asynchronously.

This method provides control over when data linked to a specific `visitor_code` is transmitted. For example, if `add_data()` is called multiple times, sending a request after each invocation would be inefficient. Instead, you can batch these updates and call `flush()` once to send all accumulated data in a single request.

The `flush()` method uses the provided `visitor_code` as the unique visitor identifier.

<Tip>
  * `flush()`: Queues a flush operation according to the configured tracking interval.
  * `flush_instant()`: Sends tracking data immediately without waiting for the interval.
</Tip>

```cpp theme={null}
// Queues a flush operation for the given visitor_code.
// Data will be sent according to the configured tracking interval (non-blocking).
client.flush(visitor_code);

// Immediately sends all pending tracking data for the given visitor_code
// and blocks until the request completes.
client.flush_instant(visitor_code);

// Immediately sends all pending tracking data without blocking.
client.flush_instant_async(visitor_code, [](std::exception_ptr error) {
    // The immediate tracking operation completed.
});

// C++20 coroutines
co_await client.co_flush_instant(visitor_code);
```

##### Parameters

| Name | Type | Description |
| - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the visitor. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |

#### get\_remote\_data()

The `get_remote_data()` method lets you retrieve remote data stored on Kameleoon servers for the specified `key`. In most setups, this data is written through the Kameleoon Data API and can later be fetched by your C++ service whenever you need additional app context.

This method is useful when you want to keep structured information on Kameleoon's remote infrastructure and reuse it from your back-end without maintaining a separate retrieval mechanism.

```cpp theme={null}
// Blocking
std::string data = client.get_remote_data("test-key");

// Completion: the value is only meaningful when `error` is null
client.get_remote_data_async("test-key", [](std::exception_ptr error, std::string data) {
    if (!error) {
        // Use `data`.
    }
});

// C++20 coroutines
std::string data = co_await client.co_get_remote_data("test-key");
```

##### Parameters

| Name | Type | Description |
| - | - | - |
| `key` <Badge color="red" size="sm">required</Badge> | `std::string` | Key associated with the remote data you want to retrieve. |

##### Return value

| Type | Description |
| - | - |
| `std::string` | Payload associated with the specified `key`. In most cases, the payload is JSON serialized as a string. The payload is a byte string and may contain arbitrary bytes. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::Network` | Thrown when the remote data request fails or the server responds with a non-success status code. |

#### get\_remote\_visitor\_data()

`get_remote_visitor_data()` retrieves Kameleoon visit data for the provided `visitor_code`. The method adds the data to local visitor storage so other SDK methods can use it for targeting decisions.

Data obtained using this method is especially useful when you want to:

* use data collected from other devices.
* access a visitor's history, such as previously viewed pages from past visits.
* use data that's only available on the client side, such as datalayer variables and front-end goal conversions.

Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases.

<Warning>
  By default, `get_remote_visitor_data()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches it to the visitor without the need to call [`add_data()`](#add_data). This is especially useful for [synchronizing custom data across multiple devices](/developer-docs/cross-device-experimentation/).
</Warning>

```cpp theme={null}
using namespace kameleoon;

// Fetch remote visitor data without any filter
// This will return all available data for the given visitor
client.get_remote_visitor_data(visitor_code);

// Create a filter to limit the returned data
RemoteVisitorDataFilter filter{
    .previous_visit_amount = 5,  // Include data from the last 5 visits
    .page_views = true,          // Include page view history
    .conversions = true,         // Include conversion events (e.g., goals, transactions)
};

// Fetch remote visitor data using the specified filter
// This will return only the data matching the filter criteria
client.get_remote_visitor_data(visitor_code, filter);

// Non-blocking form
client.get_remote_visitor_data_async(visitor_code, filter, [](std::exception_ptr error) {
    // The data is available locally when `error` is null.
});

// C++20 coroutines
co_await client.co_get_remote_visitor_data(visitor_code, filter);
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Visitor code whose data should be fetched. | |
| `filter` <Badge color="green" size="sm">optional</Badge> | `RemoteVisitorDataFilter` | Filter describing which remote visitor data should be retrieved. | `RemoteVisitorDataFilter{}` |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |
| `ErrorCode::Network` | Thrown when the remote visitor data request fails, can't be parsed, or the server responds with a non-success status code. |

##### Using parameters in get\_remote\_visitor\_data()

The `get_remote_visitor_data()` method lets you control which data is retrieved for a visitor. The same filtering approach works across goals, experiments, variations, and other visitor data.

For example, if you want to target users who converted on a goal during their last five visits, you can set `previous_visit_amount` to `5` and `conversions` to `true`.

The flexibility shown in this example isn't limited to goal data. You can use the filter to retrieve many different visitor behaviors and make them available to targeting and reporting logic in your C++ app.

##### `RemoteVisitorDataFilter` fields

| Name | Type | Description | Default |
| - | - | - | - |
| `previous_visit_amount` | `uint32_t` | Number of previous visits to retrieve data from. | `1` |
| `current_visit` | `bool` | If `true`, current visit data will be retrieved. | `true` |
| `custom_data` | `bool` | If `true`, custom data will be retrieved. | `true` |
| `visitor_code` | `bool` | If `true`, the most recent visitor code will be reused. | `true` |
| `page_views` | `bool` | If `true`, page view data will be retrieved. | `false` |
| `geolocation` | `bool` | If `true`, geolocation data will be retrieved. | `false` |
| `device` | `bool` | If `true`, device data will be retrieved. | `false` |
| `browser` | `bool` | If `true`, browser data will be retrieved. | `false` |
| `operating_system` | `bool` | If `true`, operating system data will be retrieved. | `false` |
| `conversions` | `bool` | If `true`, conversion data will be retrieved. | `false` |
| `experiments` | `bool` | If `true`, experiment data will be retrieved. | `false` |
| `kcs` | `bool` | If `true`, Kameleoon Conversion Score data will be retrieved. | `false` |
| `personalizations` | `bool` | If `true`, personalization data will be retrieved. | `false` |
| `cbs` | `bool` | If true, the SDK retrieves Contextual Bandit score data. | `false` |

#### get\_visitor\_warehouse\_audience()

This method retrieves audience data associated with a visitor in your warehouse integration by using the specified `visitor_code` and, optionally, a `warehouse_key`. The `warehouse_key` is typically your internal user ID. The `custom_data_index` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors.

When the call succeeds, the SDK converts the returned audience list into [`CustomData`](#customdata), adds it to the visitor locally, and makes it available for targeting purposes. For more background, see the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting).

```cpp theme={null}
// Fetch audience data for a visitor using only the visitor_code.
client.get_visitor_warehouse_audience(visitor_code, 98);

// Fetch audience data for a visitor using both visitor_code and warehouse_key.
// Useful when your warehouse uses a different identifier than visitor_code.
client.get_visitor_warehouse_audience(visitor_code, 98, "internal-user-id");

// Non-blocking form
client.get_visitor_warehouse_audience_async(visitor_code, 98, "internal-user-id", [](std::exception_ptr error) {
    // The audience data is available locally when `error` is null.
});

// C++20 coroutines
co_await client.co_get_visitor_warehouse_audience(visitor_code, 98, "internal-user-id");
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Visitor whose warehouse audiences should be retrieved. | |
| `custom_data_index` <Badge color="red" size="sm">required</Badge> | `uint32_t` | Custom data index configured in Kameleoon for warehouse audience targeting. | |
| `warehouse_key` <Badge color="green" size="sm">optional</Badge> | `std::optional<std::string>` | External warehouse key, usually your internal user ID. When omitted, the SDK uses `visitor_code`. | `std::nullopt` |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |
| `ErrorCode::Network` | Thrown when the warehouse audience request fails, can't be parsed, or the server responds with a non-success status code. |

#### set\_legal\_consent()

You must use this method to specify whether the visitor has given legal consent to use personal data. Setting `consent` to `false` limits the types of data that can be included in tracking requests. This helps you adhere to legal and regulatory requirements while responsibly managing visitor data. For more information, see the [consent management policy](/user-manual/project-management/consent-management-policy).

If you pass a `CookieAccessor`, the SDK also updates the visitor cookies according to the consent status.

```cpp theme={null}
// Set consent and update cookies
client.set_legal_consent(visitor_code, true, &cookies);

// Set consent without updating cookies
client.set_legal_consent(visitor_code, true);
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | The user's unique identifier. | |
| `consent` <Badge color="red" size="sm">required</Badge> | `bool` | `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent. | |
| `cookies` <Badge color="green" size="sm">optional</Badge> | `CookieAccessor*` | Optional cookie accessor used to update cookies. | `nullptr` |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |

##### Consent revocation behavior

When you call `set_legal_consent()` with `consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires.

If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically.

### Goals and third-party analytics

#### track\_conversion()

* 📨 *Sends Tracking Data to Kameleoon*

Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitor_code` and `goal_id`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitor_code` is usually identical to the one you used when triggering the experiment.

<Tip>
  This method is non-blocking because the SDK makes the server call asynchronously.
</Tip>

```cpp theme={null}
using namespace kameleoon;

// Track a goal
client.track_conversion(visitor_code, goal_id);

// Track a goal with revenue
client.track_conversion(visitor_code, goal_id, TrackConversionOptions{.revenue = 100.0F});

// Track a goal with negative revenue
client.track_conversion(
    visitor_code, goal_id, TrackConversionOptions{.revenue = 100.0F, .negative = true});

// Track a goal with custom metadata
client.track_conversion(
    visitor_code, goal_id, TrackConversionOptions{.metadata = {CustomData(4, {"true"})}});
```

##### Parameters

| Name | Type | Description | Default |
| - | - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the visitor. | |
| `goal_id` <Badge color="red" size="sm">required</Badge> | `uint32_t` | ID of the goal. | |
| `revenue` <Badge color="green" size="sm">optional</Badge> | `float` | Revenue of the conversion. | `0` |
| `negative` <Badge color="green" size="sm">optional</Badge> | `bool` | Defines if the revenue is positive or negative. | `false` |
| `metadata` <Badge color="green" size="sm">optional</Badge> | `std::vector<CustomData>` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `{}` |

<Note>
  The optional `revenue`, `negative`, and `metadata` parameters are passed as the fields of a `TrackConversionOptions` struct.
</Note>

<Note>
  metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata).

  If you provide the `metadata` parameter, Kameleoon uses these specified values for the current conversion instead of what you previously collected using the [`add_data()`](#add_data) method. If you omit the parameter, Kameleoon uses the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit.

  Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `track_conversion()` method.

  In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card').

  ```cpp theme={null}
  client.add_data(visitor_code, {
      CustomData(5, {"Credit Card"}),
      CustomData(9, {"Express Delivery"}),
  });

  client.track_conversion(
      visitor_code, goal_id, TrackConversionOptions{.metadata = {CustomData(5, {"Amex Credit Card"})}});
  ```
</Note>

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |

#### get\_engine\_tracking\_code()

Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `get_engine_tracking_code()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous 5 seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration.

Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method.

```cpp theme={null}
std::string tracking_code = client.get_engine_tracking_code(visitor_code);
```

<Note>
  * To use this feature, implement both the C++ SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because this flow uses Engine.js only for tracking, you can install the asynchronous tag before the closing `</body>` tag.
  * If you only want to track experiments in Kameleoon and don't need to send exposure events to third-party analytics tools, use the [JavaScript / TypeScript SDK](/developer-docs/sdks/web-sdks/js-sdk). This option works well for [serverless edge compute platforms](/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits). The JavaScript / TypeScript SDK automatically tracks variations when you call [`getVisitorCode`](/developer-docs/sdks/web-sdks/js-sdk#getvisitorcode), as long as you add the corresponding experiment assignments to `window.kameleoonQueue`.
  * You can insert the returned tracking code directly into an HTML `<script>` tag.

  ```html theme={null}
  <html lang="en">
    <body>
      <script>
        const engineTrackingCode = `
          window.kameleoonQueue = window.kameleoonQueue || [];
          window.kameleoonQueue.push(['Experiments.assignVariation', 123456, 7890, true]);
          window.kameleoonQueue.push(['Experiments.trigger', 123456, true]);
          window.kameleoonQueue.push(['Experiments.assignVariation', 234567, 8901, true]);
          window.kameleoonQueue.push(['Experiments.trigger', 234567, true]);
        `;
        const script = document.createElement('script');

        script.textContent = engineTrackingCode;
        document.body.appendChild(script);
      </script>

    </body>
  </html>
  ```

  In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code.
</Note>

##### Parameters

| Name | Type | Description |
| - | - | - |
| `visitor_code` <Badge color="red" size="sm">required</Badge> | `std::string` | Unique identifier of the visitor. |

##### Return value

| Type | Description |
| - | - |
| `std::string` | JavaScript code to insert into the page. |

##### Exceptions

| Type | Description |
| - | - |
| `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. |
| `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. |

### Events

#### set\_event\_handler()

Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. The client keeps at most one handler per event type: registering a new handler for the same event type replaces the previous handler.

The `EventHandler` constructor you use (`EventHandler::datafile_update()` or `EventHandler::http_request()`) selects the event type, so a handler and the events it receives can't get out of sync. Passing an empty `std::function` (`{}`) removes the current handler for the selected event type.

<Tabs>
  <Tab title="datafile_update">
    ```cpp theme={null}
    #include "kameleoon/events.hpp"

    using namespace kameleoon;

    client.set_event_handler(EventHandler::datafile_update([](const DataFileUpdateEvent& event) {
        DataFileUpdateSource source = event.source; // DataFileUpdateSource::Polling or DataFileUpdateSource::Streaming
        uint64_t date_modified = event.date_modified; // Data file modification date in milliseconds.

        // React to the data file update.
    }));

    // Clear the handler.
    client.set_event_handler(EventHandler::datafile_update({}));
    ```

    <Tip>
      `DataFileUpdateEvent` contains information about an SDK data file update.

      | Name | Type | Description |
      | - | - | - |
      | `source` | `DataFileUpdateSource` | The update source. [`DataFileUpdateSource::Polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`DataFileUpdateSource::Streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. |
      | `date_modified` | `uint64_t` | The modification date of the updated data file, in milliseconds. |
    </Tip>
  </Tab>

  <Tab title="http_request">
    ```cpp theme={null}
    #include "kameleoon/events.hpp"

    #include <variant>

    using namespace kameleoon;

    client.set_event_handler(EventHandler::http_request([](const HttpRequestEvent& event) {
        if (const auto* succeeded = std::get_if<HttpRequestSucceeded>(&event)) {
            // The SDK request completed successfully.
            RequestType request_type = succeeded->request_type;
            uint16_t http_status = succeeded->http_status;
            std::chrono::milliseconds duration = succeeded->duration;
        } else if (const auto* failed = std::get_if<HttpRequestFailed>(&event)) {
            // The SDK request failed.
            HttpRequestFailureReason reason = failed->failure.reason; // HttpStatus, Error, or Cancelled
            std::optional<uint16_t> http_status = failed->failure.http_status;
            std::optional<std::string> cause = failed->failure.cause;
        }
    }));

    // Clear the handler.
    client.set_event_handler(EventHandler::http_request({}));
    ```

    <Tip>
      `HttpRequestEvent` is a `std::variant<HttpRequestSucceeded, HttpRequestFailed>`. The SDK reports the event once per each actual HTTP request attempt, including retries. Use `std::get_if` or `std::visit` to tell a successful request from a failed one.

      | Type | Description |
      | - | - |
      | `HttpRequestSucceeded` | Describes an SDK HTTP request that completed successfully. Always carries `http_status`. |
      | `HttpRequestFailed` | Describes an SDK HTTP request that failed because of an HTTP status, error, or cancellation. Always carries `failure`. |

      <Accordion title="Event data fields and failure details">
        ##### Event data fields

        | Name | Type | Description |
        | - | - | - |
        | `request_type` | `RequestType` | The SDK request type. Possible values are `RequestType::DataFile`, `RequestType::Tracking`, `RequestType::RemoteVisitorData`, `RequestType::RemoteData`, and `RequestType::AccessToken`. |
        | `http_status` | `uint16_t` | The HTTP status code returned by the request. Present only in `HttpRequestSucceeded`. |
        | `failure` | `HttpRequestFailure` | Details about why the request failed. Present only in `HttpRequestFailed`. |
        | `duration` | `std::chrono::milliseconds` | The request duration. |

        ##### HttpRequestFailure

        `HttpRequestFailure` contains details about a failed SDK HTTP request.

        | Name | Type | Description |
        | - | - | - |
        | `reason` | `HttpRequestFailureReason` | The failure reason. Possible values are `HttpRequestFailureReason::HttpStatus`, `HttpRequestFailureReason::Error`, and `HttpRequestFailureReason::Cancelled`. |
        | `http_status` | `std::optional<uint16_t>` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `std::nullopt` for exception and cancellation failures. |
        | `cause` | `std::optional<std::string>` | The exception (error) that caused the request to fail. This value is `std::nullopt` when the request failed because of an HTTP status or cancellation. |
      </Accordion>
    </Tip>
  </Tab>
</Tabs>

<Note>
  Handlers are called synchronously on the SDK's single worker thread, so they must not block: time spent in a handler delays data file polling and tracking flushes. They must be thread-safe and must not throw: an exception escaping a handler is caught and discarded, and never affects SDK behavior. Don't call blocking SDK operations from a handler; use the `*_async` forms and finish the work on an application thread instead.

  An invocation already in progress may finish after you clear the handler or destroy the client, and the handler (with its captures) is destroyed on an SDK thread once it's no longer in use. Keep captured objects alive and thread-safe accordingly. A handler may clear or replace itself.

  All `KameleoonClient` instances created for the same site code and environment share one handler per event type: the last handler set wins, and destroying any of those instances clears it. Keep a single client instance per site code and environment.
</Note>

##### Parameters

| Name | Type | Description |
| - | - | - |
| `handler` <Badge color="red" size="sm">required</Badge> | `EventHandler` | The handler to register, built with `EventHandler::datafile_update()` from a `DataFileUpdateHandler` (`std::function<void(const DataFileUpdateEvent&)>`), or with `EventHandler::http_request()` from an `HttpRequestHandler` (`std::function<void(const HttpRequestEvent&)>`). Pass an empty `std::function` to remove the current handler for that event type. |

### Data types

This section lists the C++ data types declared by the SDK in the `kameleoon` namespace (`kameleoon/data/*.hpp`). All of them are plain structs that you pass to [`add_data()`](#add_data).

#### ApplicationVersion

`ApplicationVersion` represents the semantic version number of your application.

<Tip>
  A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one.
</Tip>

| Name | Type | Description |
| - | - | - |
| `value` <Badge color="red" size="sm">required</Badge> | `std::string` | The app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. |

```cpp theme={null}
using namespace kameleoon;

// major
client.add_data(visitor_code, {ApplicationVersion{"10"}});
// major.minor
client.add_data(visitor_code, {ApplicationVersion{"10.20"}});
// major.minor.patch
client.add_data(visitor_code, {ApplicationVersion{"10.20.30"}});
```

#### Browser

The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it.

| Name | Type | Description |
| - | - | - |
| `type` <Badge color="red" size="sm">required</Badge> | `BrowserType` | List of browsers: `BrowserType::Chrome`, `BrowserType::InternetExplorer`, `BrowserType::Firefox`, `BrowserType::Safari`, `BrowserType::Opera`, `BrowserType::Other`. |
| `version` <Badge color="green" size="sm">optional</Badge> | `std::optional<float>` | Version of the browser, floating point number represents major and minor version of the browser |

```cpp theme={null}
using namespace kameleoon;

// Browser data with a version
client.add_data(visitor_code, {Browser{.type = BrowserType::Safari, .version = 26.4F}});
// Browser data without a version
client.add_data(visitor_code, {Browser{.type = BrowserType::Chrome}});
```

#### Conversion

The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it.

<Tip>
  * Each visitor can have multiple `Conversion` objects.
  * You can find the `goal_id` in the Kameleoon app.
</Tip>

| Name | Type | Description | Default |
| - | - | - | - |
| `goal_id` <Badge color="red" size="sm">required</Badge> | `uint32_t` | ID of the goal. | |
| `revenue` <Badge color="green" size="sm">optional</Badge> | `float` | Revenue of the conversion | `0` |
| `negative` <Badge color="green" size="sm">optional</Badge> | `bool` | Defines if the revenue is positive or negative. | `false` |
| `metadata` <Badge color="green" size="sm">optional</Badge> | `std::vector<CustomData>` | Metadata of the conversion. | `{}` |

```cpp theme={null}
using namespace kameleoon;

// Add a simple conversion with ID 32
client.add_data(visitor_code, {Conversion{.goal_id = 32}});

// Add conversion with ID 33 including revenue and marked as negative
client.add_data(visitor_code, {Conversion{.goal_id = 33, .revenue = 10.0F, .negative = true}});

// Add conversion with ID 34 including revenue, negative flag, and custom metadata
client.add_data(visitor_code, {
    Conversion{
        .goal_id = 34,
        .revenue = 10.0F,
        .negative = true,
        .metadata = {
            CustomData(3, {"metadata1", "md2"}),
            CustomData(5, {"md3"}),
        },
    },
});
```

#### Cookie

`Cookie` contains information about the cookies stored on the visitor's device.

| Name | Type | Description |
| - | - | - |
| `cookies` | `std::unordered_map<std::string, std::string>` | A string map containing cookie keys and values. |

<Tip>
  Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one.
</Tip>

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {Cookie{.cookies = {{"segment", "vip"}}}});
```

#### CustomData

`CustomData` enables the association of any type of data with each visitor, making it an effective tool for targeting conditions in [segments](/user-manual/assets/segments/create-a-segment/). Additionally, it can be used as a filter or breakdown in experiment reports. For more information about custom data, refer to this [article](/developer-docs/custom-data).

Define custom data types in the Kameleoon app or the Data API and use them from the SDK.

`CustomData` has two constructors: one identifies the custom data by its `id` (index), the other by its `name`.

| Name | Type | Description | Default |
| - | - | - | - |
| `id`/`name` <Badge color="red" size="sm">required</Badge> | `uint32_t`/`std::string` | Index or Name of the custom data. **Either `id` or `name` must be provided** to identify the data. | |
| `values` <Badge color="red" size="sm">required</Badge> | `std::vector<std::string>` | Values of the custom data to be stored. | |
| `overwrite` <Badge color="green" size="sm">optional</Badge> | `bool` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` |

<Note>
  * Each visitor is allowed only one `CustomData` for each unique `id`(`name`). Adding another `CustomData` with the same `id`(`name`) will replace the existing one.

  * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column.

  * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data.

  * Adding a `CustomData` instance created with a name when the SDK instance isn't initialized or the name isn't registered, will result in the data being ignored.
</Note>

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {CustomData(1, {"value"})});

// With several values
client.add_data(visitor_code, {CustomData(1, {"value1", "value2"})});

// To set the `overwrite` flag to false
client.add_data(visitor_code, {CustomData(1, {"value"}, false)});

// To use a name instead of the index
client.add_data(visitor_code, {CustomData("my-custom-data", {"value"})});

// To use a name instead of the index and set the `overwrite` flag to false
client.add_data(visitor_code, {CustomData("my-custom-data", {"value"}, false)});
```

#### Device

You can use device data to filter experiment and personalization reports by any associated value.

| Name | Type | Description |
| - | - | - |
| `type` | `DeviceType` | Device type. Possible values are `DeviceType::Phone`, `DeviceType::Tablet`, and `DeviceType::Desktop`. |

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {Device{DeviceType::Desktop}});
```

#### Geolocation

`Geolocation` contains the visitor's geolocation details.

| Name | Type | Description |
| - | - | - |
| `country` <Badge color="red" size="sm">required</Badge> | `std::string` | The country of the visitor. |
| `region` <Badge color="green" size="sm">optional</Badge> | <nobr>`std::optional<std::string>`</nobr> | The region of the visitor. |
| `city` <Badge color="green" size="sm">optional</Badge> | <nobr>`std::optional<std::string>`</nobr> | The city of the visitor. |
| `postal_code` <Badge color="green" size="sm">optional</Badge> | <nobr>`std::optional<std::string>`</nobr> | The postal code of the visitor. |
| `latitude` <Badge color="green" size="sm">optional</Badge> | `std::optional<float>` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. |
| `longitude` <Badge color="green" size="sm">optional</Badge> | `std::optional<float>` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. |

<Tip>
  * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one.
</Tip>

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {
    Geolocation{
        .country = "France",
        .region = "Ile-de-France",
        .city = "Paris",
        .postal_code = "75009",
        .latitude = 48.8720171F,
        .longitude = 2.3338352F,
    },
});
```

#### OperatingSystem

`OperatingSystem` contains information about the operating system on the visitor's device.

| Name | Type | Description |
| - | - | - |
| `type` | `OperatingSystemType` | Operating system family. Possible values are `OperatingSystemType::Windows`, `OperatingSystemType::Mac`, `OperatingSystemType::IOS`, `OperatingSystemType::Linux`, `OperatingSystemType::Android`, and `OperatingSystemType::WindowsPhone`. |

<Tip>
  Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one.
</Tip>

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {OperatingSystem{OperatingSystemType::Windows}});
```

#### PageView

Store page view events.

| Name | Type | Description | Default |
| - | - | - | - |
| `url` | `std::string` | URL of the page viewed. | |
| `title` | `std::optional<std::string>` | Title of the page viewed. | `std::nullopt` |
| `referrers` | `std::vector<int32_t>` | Referrer indices of previously viewed pages. | `{}` |

<Note>
  The referrer index is available in the Kameleoon app on the [acquisition channel configuration](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) page. Be careful: the index starts at `0`, so the first acquisition channel you create has the ID `0`, not `1`.
</Note>

```cpp theme={null}
using namespace kameleoon;

// Full initialization with url, title, and referrers
client.add_data(visitor_code, {PageView{.url = "https://example.com", .title = "Homepage", .referrers = {3}}});

// Minimal initialization, only requires a URL
client.add_data(visitor_code, {PageView{.url = "https://example.com"}});
```

#### UniqueIdentifier

If you don't add `UniqueIdentifier` for a visitor, `visitor_code` is used as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation/). When you add `UniqueIdentifier{true}`, the SDK links flushed data with the visitor associated with the specified identifier.

This can be useful in situations where you can't access the anonymous `visitor_code` originally assigned to a visitor, but you do have access to an internal identifier connected to that visitor through session merging.

| Name | Type | Description |
| - | - | - |
| `value` | `bool` | Whether the current `visitor_code` should be treated as a unique identifier. |

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {UniqueIdentifier{true}});
```

#### UserAgent

Server-side experiments are more likely to be affected by bot traffic than client-side experiments. Kameleoon uses the IAB/ABC International Spiders and Bots List to recognize known bots and spiders, and it also uses the `UserAgent` field to filter out other unwanted traffic that might distort your conversion metrics. For more information, see the help article on [bot filtering](/user-manual/experiment-analytics/troubleshooting/data-discrepancies#how-does-kameleoon-filter-bot-traffic-from-my-results).

If you use internal bots, send the user-agent value `curl/8.0` to exclude them from analytics.

| Name | Type | Description |
| - | - | - |
| `value` | `std::string` | User-Agent value sent with tracking requests. |

```cpp theme={null}
using namespace kameleoon;

client.add_data(visitor_code, {UserAgent{"Mozilla/5.0"}});
```

### Returned types

#### DataFile

The `DataFile` contains the SDK configuration details.

It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager.

| Name | Type | Description |
| - | - | - |
| `feature_flags` | `std::unordered_map<std::string, FeatureFlag>` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. |
| `date_modified` | `uint64_t` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. |

```cpp theme={null}
// Retrieves the map of feature flags from the DataFile.
// The map is keyed by feature flag identifiers, with each value being a FeatureFlag object.
const std::unordered_map<std::string, FeatureFlag>& feature_flags = datafile.feature_flags;

// Retrieves the last modification timestamp of the DataFile.
// The value is a uint64_t representing milliseconds since the Unix epoch.
uint64_t date_modified = datafile.date_modified;
```

#### FeatureFlag

The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details).

It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager.

| Name | Type | Description |
| - | - | - |
| `environment_enabled` | `bool` | Indicating whether the feature flag is enabled in the current environment. |
| `default_variation_key` | `std::string` | The key of the default variation associated with the feature flag. |
| `variations` | `std::unordered_map<std::string, Variation>` | A map of `Variation` objects, keyed by variation keys. |
| `rules` | `std::vector<Rule>` | A list of `Rule` objects |

```cpp theme={null}
// Check whether the feature flag is enabled in the current environment.
bool environment_enabled = feature_flag.environment_enabled;

// Retrieve the key of the default variation.
const std::string& default_variation_key = feature_flag.default_variation_key;

// Retrieve the default variation object (nullptr if the key isn't in `variations`).
const Variation* default_variation = feature_flag.default_variation();

// Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object).
const std::unordered_map<std::string, Variation>& variations = feature_flag.variations;

// Retrieve all targeting rules associated with the feature flag.
const std::vector<Rule>& rules = feature_flag.rules;
```

#### Rule

The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)).

It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager.

| Name | Type | Description |
| - | - | - |
| `variations` | `std::unordered_map<std::string, Variation>` | A map of `Variation` objects, keyed by variation keys. |

```cpp theme={null}
// Retrieve all variations of the rule as a map (key = variation key, value = Variation object)
const std::unordered_map<std::string, Variation>& variations = rule.variations;
```

#### Variation

`Variation` contains information about the visitor's assigned variation, or the default variation when no specific assignment exists.

| Name | Type | Description |
| - | - | - |
| `name` | `std::string` | The name of the variation. |
| `key` | `std::string` | The unique key identifying the variation. |
| `id` | `std::optional<uint32_t>` | The ID of the assigned variation, or `std::nullopt` for a default variation. |
| `experiment_id` | `std::optional<uint32_t>` | The ID of the experiment associated with the variation, or `std::nullopt` for a default variation. |
| `variables` | `std::vector<Variable>` | Variables associated with the variation. This collection can be empty when no variables are attached. |

<Note>
  * `Variation` describes the assigned or default variation, while [`Variable`](#variable) contains the details of each individual variable.
  * `id` and `experiment_id` can be `std::nullopt`, which indicates a default variation that's not tied to a specific experiment assignment.
</Note>

Additional helper methods:

| Method | Return type | Description |
| - | - | - |
| `active()` | `bool` | Returns `false` for the `off` variation. |
| `get_variable(key)` | `const Variable*` | Returns a pointer to the variation variable with the given key, or `nullptr` if not found. |

```cpp theme={null}
// Retrieving the variation name
const std::string& variation_name = variation.name;

// Retrieving the variation key
const std::string& variation_key = variation.key;

// Retrieving the variation id
std::optional<uint32_t> variation_id = variation.id;

// Retrieving the experiment id
std::optional<uint32_t> experiment_id = variation.experiment_id;

// Retrieving the variables vector
const std::vector<Variable>& variables = variation.variables;

// Checking if the variation is active (i.e., currently being served to visitors)
bool is_active = variation.active();

// Retrieving a variable by its key, returning `nullptr` if not found
if (const Variable* variable = variation.get_variable("title")) {
    // Use `variable`.
}
```

#### Variable

`Variable` contains information about a variable associated with the assigned variation.

| Name | Type | Description |
| - | - | - |
| `key` | `std::string` | The unique key identifying the variable. |
| `kind` | `std::string` | The variable type. Possible values include `BOOLEAN`, `NUMBER`, `STRING`, `JSON`, `JS`, and `CSS`. |
| `value` | [`JsonValue`](#jsonvalue) | The value of the variable. Depending on `kind`, it can hold a boolean, number, string, JSON string, JavaScript snippet, or CSS snippet. |

```cpp theme={null}
// Retrieve the list of variables associated with the variation
const std::vector<Variable>& variables = variation.variables;

// Access the variable key
const std::string& variable_key = variable.key;

// Access the variable type (kind) for conditional handling
const std::string& kind = variable.kind;

// Extract the value as a number (returns `std::nullopt` if not a number)
std::optional<double> number = variable.value.as_number();

// Extract the value as a boolean (returns `std::nullopt` if not a boolean)
std::optional<bool> apply_discount = variable.value.as_bool();

// Extract the value as a string (returns `std::nullopt` if not a string-carrying kind)
std::optional<std::string> title = variable.value.as_string();
```

<a id="jsonvalue" />

##### JsonValue

`JsonValue` represents the value of a variation variable in C++. The `kind` field tells the string-carrying kinds apart; `value` is a `std::variant<bool, double, std::string>`.

| Kind | Stored type | Description |
| - | - | - |
| `JsonValueKind::Boolean` | `bool` | Represents a boolean value. |
| `JsonValueKind::Number` | `double` | Represents a numeric value. |
| `JsonValueKind::String` | `std::string` | Represents a string value. |
| `JsonValueKind::JSON` | `std::string` | Represents a JSON-encoded value. |
| `JsonValueKind::JS` | `std::string` | Represents a JavaScript code snippet. |
| `JsonValueKind::CSS` | `std::string` | Represents a CSS code snippet. |

Typed accessors return `std::nullopt` when the value is of another kind:

| Method | Return type | Description |
| - | - | - |
| `as_bool()` | `std::optional<bool>` | The boolean value. |
| `as_number()` | `std::optional<double>` | The numeric value. |
| `as_string()` | `std::optional<std::string>` | The string value for every string-carrying kind (`String`, `JSON`, `JS`, and `CSS`). |
