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

# API reference

> Every type, method, callback and error of the Kotlin Video SDK.

`format()` and `load()` may be called from any dispatcher (their work runs on the main thread); every other call is main-thread, and every listener and callback is delivered on the main thread. Times are milliseconds; prices are CPM. The package is `so.kontext.video`.

## `KontextVideo`

The entry point, one object: `KontextVideo`.

**initialize(context, configuration, listener)** `method`

Starts the SDK: stores the configuration and calls the server in the background. Call it once, in `Application.onCreate`; later calls are ignored. `listener` is optional and is told the resulting `State` on the main thread once the server answered or failed.

```kotlin theme={null}
KontextVideo.initialize(this, Configuration("…")) { state -> … }
```

**awaitInitialization()** `suspend method`

Waits until the server answered or failed and returns the `State`; at once when it already has: `Enabled`, `Disabled(reason)` (the server switched the SDK off for this launch) or `Failed` (the server could not be reached; the next ads request tries again first).

```kotlin theme={null}
val state = KontextVideo.awaitInitialization()
```

**state** `State`

The current state; also `NotInitialized` before `initialize` and `Initializing` while the server is asked.

**user** `User?`

What you know about the viewer. Set it whenever you learn it; the next ads request carries it.

**regulatory** `Regulatory?`

Privacy and consent signals. The SDK reads the IAB values your consent platform writes to the default `SharedPreferences`; set here what it did not write (COPPA always comes from you).

**variantId** `String?`

Selects which type of ad to render, for A/B tests agreed with Kontext.

**VERSION** `String`

The SDK's version, as sent with every request. For your logs and crash reports.

## `Configuration`

Built as `Configuration("…") { logLevel = … }`.

**publisherToken** `String` *required*

Identifies the publisher. Not a secret. You get a `{publisher}-dev` token for testing and a production one.

**logLevel** `LogLevel`

How much the SDK prints to Logcat, tag `KontextVideo`: `NONE`, `ERROR`, `WARNING` (default), `INFO`, `DEBUG`.

**adServerUrl** `String`

The ad server, by default the production one. Change it only for testing against a server Kontext gives you.

## `Content`

The video the ads are for. Everything beyond `id` and `language` is contextual signal. Built as `Content(id, language) { title = … }`.

**id** `String` *required*

Your own id of the video, any scheme.

**language** `String` *required*

BCP-47, for example `"en"` or `"pt-BR"`.

**mediaRating** `Rating?`

`SAFE` or `EXPLICIT`. Leave unset when you don't know; explicit content gets no ads that require safe content.

**url** `String?`

The episode's public page.

**title** `String?`

**categories** `List\<String\>?`

IAB Content Taxonomy 3.0 ids.

**description** `String?`

**durationMillis** `Long?`

Milliseconds.

**episodeNumber** `Int?`

**keywords** `List\<String\>?`

**genre** `String?`

For example `"romance"`.

**series** `Content.Series?`

`Content.Series(id) { title = …; episodeCount = … }`.

**Properties**

**id** `String` *required*

**title** `String?`

**description** `String?`

**episodeCount** `Int?`

Total number of episodes.

## `User`

Built as `User { id = … }`.

**id** `String?`

Your own unique identifier of the viewer.

**email** `String?`

Plain email address, when consent allows it. Sent as given; the server hashes it on arrival and never stores the plain address.

**emailHash** `String?`

SHA-256 of the normalized address (trimmed, lowercased, UTF-8), 64 lowercase hex characters. Sent as given.

**keywords** `List\<String\>?`

The viewer's interests, in your own words.

## `Regulatory`

Built as `Regulatory { coppa = true }`.

**gdpr** `Boolean?`

Whether the request is subject to GDPR.

**gdprConsent** `String?`

IAB TCF v2 consent string.

**coppa** `Boolean?`

Whether the request is subject to COPPA. Cannot be read from the device.

**gpp** `String?`

IAB Global Privacy Platform string.

**gppSid** `List\<Int\>?`

GPP section ids that apply.

**usPrivacy** `String?`

Legacy IAB US Privacy (CCPA) string.

## `AdFormat`

`OVERLAY`, `PAUSE`, `LINEAR`.

## `VideoAds`

The ads of one video. Create one per episode; creating it sends nothing. Any number can be alive at once. Call `destroy()` when the viewer leaves the video: dropping the reference does not end its ads.

**VideoAds(content, labels)** `constructor`

`labels` is optional: key-values that say where these ads play, such as the viewer's stage in the series or the player's layout. Every request of this `VideoAds` carries them, and your setup on our side decides what they change, such as which formats are offered. They are fixed for the object's life and describe the screen, never the viewer. Keys use `a-z`, `0-9` and `_`; values are not empty; both are at most 64 bytes, with at most 20 pairs. Anything else is dropped with a Logcat warning.

```kotlin theme={null}
val ads = VideoAds(Content("episode-1", "en"))
val firstEpisode = VideoAds(content, mapOf("stage" to "first_episode", "layout" to "fullscreen"))
```

**format(format)** `suspend method`

The ad for one format: the same `VideoAd` as last time while it is unshown and valid, a new one otherwise. Calls made while a request runs share it. A failed request returns the held ad when it is still valid.

Throws `NoFill`, `Request`, `Destroyed`, `Disabled`, `NotInitialized`, `InitializationFailed`. A cancelled coroutine stops waiting; the request itself continues.

```kotlin theme={null}
val ad = ads.format(AdFormat.PAUSE)
```

**format(format, callback)** `method`

The same for code without coroutines: `callback` is a `VideoAdsCallback<VideoAd>` and gets `onSuccess(ad)` or `onError(exception)` on the main thread.

**track(player)** `method`

The player this episode plays in: a Media3 `Player` (`import so.kontext.video.media3.track`, from `so.kontext:video-media3`), or any [`VideoAdsPlayer`](#videoadsplayer). The SDK reads its time for requests and events, and follows its playback for an overlay and a pause ad: an overlay shows over playing content only, and ends with `onAdEnded(INTERRUPTED)` when the content pauses, ends, or the player moves to another item; a pause ad ends with `onAdEnded(INTERRUPTED)` when the content plays again after a pause, wherever the viewer resumed it (the media notification, the lock screen, a headset, picture-in-picture). It ends this way only after the content was seen paused while the ad was on screen: a pause ad shown over playing content stays until the content pauses and plays again, and an overlay shown right after the content resumes ends the pause ad and shows. Held until `destroy()` or the next `track`; the Media3 adapter holds your player weakly. Call it when this episode starts playing in the player. Without a player, events carry 0 (right for a prefetch), an overlay runs on its own time and a pause ad stays until it is dismissed or closed. The first `track` while an overlay is on screen keeps it; tracking a different player ends it with `onAdEnded(INTERRUPTED)`.

```kotlin theme={null}
ads.track(player)
```

**destroy()** `method`

Releases the video's ads. Every held ad is dropped and the advertiser told it was not used; the ad on screen ends with `onAdEnded(DESTROYED)`; a `format()` or `load()` in flight throws `Destroyed`. Idempotent.

Call it with whatever owns the `VideoAds`. A `ViewModel`: in `onCleared()`, and in every new Activity `track` its player and set the ads' listeners again, so the ads survive a rotation. An Activity, a Fragment or a Compose `DisposableEffect`: in its `onDestroy()` (`onDispose`), every time, with `android:configChanges` declared for rotation so a rotation does not recreate it. A `VideoAds` nobody destroys is never released.

**content** `Content`

What it was created with.

**isDestroyed** `Boolean`

`true` after `destroy()`.

**lastRequestId** `String?`

The id of the latest ads request, `null` before the first. Quote it to Kontext when an ad or a no-fill looks wrong.

## `VideoAdsPlayer`

What the SDK reads from a player that is not a Media3 `Player`. Implement it, in a small adapter around your player, and pass it to `track(player)`. The SDK reads it on the main thread.

**currentPositionMillis** `Long`

Where the content is, in milliseconds of the episode.

**isPlaying** `Boolean`

`false` when the viewer or your app paused the content, or it ended. Buffering and seeking count as playing.

**playbackSpeed** `Float?`

Optional. The playback speed, 1 being normal, when your player knows it. `null` (the default) allows up to about 4×.

**addListener(listener) / removeListener(listener)** `method`

Optional. Tell the `VideoAdsPlayer.Listener` what the player does, as it happens, so an overlay reacts at once: `onIsPlayingChanged(isPlaying)`, `onSeek()`, `onContentChanged()` (another item, or this one played to its end). Without a listener the SDK reads `isPlaying` four times a second while an overlay is on screen, and estimates seeks from the position.

An overlay's duration counts milliseconds of video. Without `onSeek()` and `playbackSpeed`, the SDK treats a jump faster than about 4× real time as a seek and counts nothing for it. The Media3 adapter reports all of it.

```kotlin theme={null}
class MyPlayerAdapter(private val player: MyPlayer) : VideoAdsPlayer {
    override val currentPositionMillis: Long get() = player.positionMillis
    override val isPlaying: Boolean get() = player.state == State.PLAYING || player.state == State.BUFFERING
}
```

## `VideoAd`

One ad, from the server's answer to the end of its presentation. Look at its facts, then `load()` or `reject()`, then `show()`, then exactly one `onAdEnded`. Shown at most once.

### Facts

**id** `String`

**format** `AdFormat`

**price** `VideoAd.Price`

**Properties**

**value** `Double`

CPM.

**currency** `String`

ISO 4217.

**precision** `Precision`

`EXACT` (a DSP's clearing price) or `ESTIMATED` (house demand, a forecast).

**durationMillis** `Long?`

Overlay: how long the SDK keeps it on screen, counting only time it is seen; `null` when it stays until ended. Linear: the video's length. Always `null` for a pause ad.

### State

**isLoaded** `Boolean`

`load()` finished.

**isReady** `Boolean`

Loaded, not shown, not rejected, not expired, and the SDK not switched off by the server: `show()` can be tried. A hint, not a guarantee; `show()` still measures the container.

**invalidationReason** `InvalidationReason?`

Why the ad ended before it was shown, once it did: `Expired`, `Rejected`, `Destroyed`, `CreativeFailed(code)`.

**wasShown** `Boolean`

`true` from a successful `show()` on.

**hasEnded** `Boolean`

`true` once the ad began to end. `onAdEnded` follows at once for an overlay or pause ad, and once its screen is gone for a linear ad.

**isCompleted** `Boolean`

Whether the creative ran to its end: a video reached complete, an overlay's duration elapsed. Separate from how it left the screen.

**listener** `VideoAdListener?`

Receives the ad's moments. Held until `onAdEnded` has run, then let go.

### Methods

**load()** `suspend method`

Renders the creative off screen and returns once it is ready, so `show()` is instant. Throws `CreativeFailed`, `Invalidated`, `Destroyed`, `Disabled`. A second call waits for the first. `load(callback)` is the same for code without coroutines: a `VideoAdsCallback<VideoAd>`.

**reject(reason)** `method`

You won't use the ad; the server tells the advertiser. `RejectReason.NotUsed` (default) or `RejectReason.Outbid(winningPrice, winner)`. Works until `show()`, loaded or not. Throws `AlreadyShown` after it, or `Invalidated` if the ad already ended.

```kotlin theme={null}
try {
    ad.reject(VideoAd.RejectReason.Outbid(winningPrice = 2.4, winner = "Other network"))
} catch (e: VideoAdException) { }   // the ad already ended
```

**show(container, insets)** `method`

Shows an overlay or pause ad inside `container`, the `ViewGroup` the content plays in. Returning means the ad is on screen, or your own code that ran during the call already ended it: `onAdEnded` arrived and `hasEnded` is `true`. The ad follows the container when it resizes or rotates; a container that becomes too small for it ends the ad with `onAdEnded(INTERRUPTED)`. Throws, with nothing shown: `NotLoaded`, `AlreadyShown`, `AnotherAdShowing`, `Invalidated`, `Destroyed`, `Disabled`, `PresenterUnavailable` (the container is not attached to a window, not shown, has no area, or is not on screen in its window, such as a pager's neighbour page: show on the current page only; its Activity is stopped, finishing or destroyed, or the app is in the background; or the container refuses a child, such as a `ScrollView` that already has one or a `ListView`), `ContainerTooSmall` (its padding and your insets count against the space) and `ContentPaused` (an overlay over paused content; the ad stays ready in these three), `WrongPresentation` (a linear ad). With a player given to `track`, an overlay also ends with `onAdEnded(INTERRUPTED)` when the content stops, and a pause ad when the content plays again.

**Parameters**

**insets** `androidx.core.graphics.Insets`

Space to keep free inside `container`, in pixels, on top of the system bars and display cutout that overlap it: `Insets.of(0, 0, 0, 200)` places a bottom strip 200 px above them. Defaults to `Insets.NONE`. Kept while the ad is shown, through resizes and rotations. A full-screen pause ad ignores them. See [Keeping the ad clear of your controls](https://docs.kontext.so/video/sdk/kotlin/guides/insets.md).

**show(activity)** `method`

Shows a linear ad full screen, in the SDK's own Activity started from `activity`. Back does nothing there: the creative's own controls end the ad. Throws, with nothing shown: the errors above, `PresenterUnavailable` for an Activity that is stopped, finishing, destroyed or in picture-in-picture (the ad stays ready; try again once it is visible), `WrongPresentation` (an overlay or pause ad). A started Activity that is not resumed yet is fine: `onStart`, an Activity Result or permission callback, `onNewIntent`. An Activity with `launchMode="singleInstance"` is not supported (the ad opens in another task, and the viewer returns there): use `standard`, `singleTop` or `singleTask`.

```kotlin theme={null}
try {
    linear.show(this)   // from your Activity
} catch (e: VideoAdException) {
    player.play()       // not shown: nothing else follows
}
```

**dismiss()** `method`

Ends an overlay or pause ad because you say so; `onAdEnded(DISMISSED)` follows, synchronously. Before `show()` and after the end it does nothing. Throws `WrongPresentation` for a linear ad, which only the SDK dismisses.

Kotlin does not make you catch exceptions, but `reject` and `show` throw for routine outcomes (the ad expired, another ad is showing): wrap every `show` and `reject` in a `try`.

## `VideoAdListener`

Delivered on the main thread. Every method has an empty default; implement the ones you need. Nothing is delivered before `show()`. After it: the impression and clicks, in no fixed order, then exactly one `onAdEnded`, always last. Every method gets the ad first, so one listener can serve several ads.

**onAdImpression(ad)** `method`

The impression was counted: at `show()` for an image or HTML creative, at the first frame for a video. At most once; may not come at all, when the ad ends before.

**onAdClicked(ad, click)** `method`

The viewer tapped the ad. The SDK recorded the click and started opening its target. Delivered as the opening starts, so whether anything opened, and which, is not reported. Once per tap.

Web links, store links and app deep links open, links into your own app included. System actions (`tel:`, `sms:` and the like) are refused; the ad's fallback web link opens instead when it has one.

**click**

**area** `VideoAd.Click.Area`

Where the viewer tapped: `Creative` (the image, the video, the card), `EndCard` (a linear ad's end card, after the video completed), or `Other(label)`, a label of the creative's own such as `"cta"`.

**onAdFailed(ad, error)** `method`

The ad broke after `show()` returned. `onAdEnded(FAILED)` follows.

**error**

**CreativeFailed** `VideoAdException`

The creative failed or stalled on screen. `code` is the VAST error code: the creative's own, or 405.

**PresenterUnavailable** `VideoAdException`

The linear ad's screen never started.

**onAdEnded(ad, reason)** `method`

Presentation is over. Exactly once per successful `show()`, always last. Resume your content here, deciding from the format and `reason`. For an overlay or pause ad it comes synchronously, even from inside your own `dismiss()` or `destroy()`. For a linear ad it comes once the ad's screen is gone, so you can start your next screen from it.

**reason**

**reason** `VideoAd.EndReason`

Who or what took the ad off the screen; see [`VideoAd.EndReason`](#videoad-endreason). Whether the creative ran to its end is `ad.isCompleted`: a linear ad whose end card the viewer closed is `CLOSED` with `isCompleted == true`.

## `VideoAd.EndReason`

| Reason | When |
| - | - |
| `COMPLETED` | The ad ended by itself: an overlay's duration elapsed, a linear video finished with no end card. |
| `SKIPPED` | The viewer tapped Skip on a linear ad before the video ended. |
| `CLOSED` | The viewer tapped the ad's close control (an overlay's or pause card's ✕, a linear end card's ✕). A pause card's ✕ leaves the content paused. |
| `RESUMED` | The viewer tapped Resume on a full-screen pause ad. Content resumed anywhere else ends it with `INTERRUPTED`. |
| `FAILED` | The creative failed or stalled, or the linear ad's screen never started; `onAdFailed` came first. |
| `INTERRUPTED` | The presentation disappeared for a reason the SDK did not cause: the container left the window or became too small for the ad, or the linear ad's screen was destroyed under it. Also an overlay or pause ad out of sight for about a second while the app is active, such as a feed page swiped away while its view stays attached. For an overlay with a player given to `track`, also the content pausing, ending or moving to another item; for a pause ad, the content playing again, wherever it was resumed. On a pause ad it does not by itself mean the content plays: check your player's state before resuming or not. |
| `DISMISSED` | You called `dismiss()`. |
| `DESTROYED` | You called `destroy()`. |

## `VideoAdException`

Thrown by a call: a thrown exception means nothing happened on screen and nothing was fired, except a failed `load()`'s VAST error event. Two are also delivered through `onAdFailed` after a `show()` that succeeded: `CreativeFailed` and `PresenterUnavailable`; the ad is over then. Every one is a subclass of `VideoAdException`.

| Exception | When |
| - | - |
| `NotInitialized` | `KontextVideo.initialize` was never called. |
| `InitializationFailed` | The server could not be reached, and the retry failed too. Every `format()` retries. |
| `Destroyed` | The `VideoAds` was destroyed. Create a new one for the next content. |
| `Disabled(reason)` | The server switched the SDK off for this launch. |
| `NoFill(reason)` | The server had nothing for this format. See [`NoFillReason`](#nofillreason). |
| `Request` | The request failed and no valid ad was held for the format; `cause` says why. |
| `CreativeFailed(code)` | The creative did not load within 10 s of active time; `code` is the VAST error code. |
| `AlreadyShown` | Shown once; cannot be shown again or rejected. |
| `AnotherAdShowing` | Another ad is on screen, from any `VideoAds`. This ad stays ready. |
| `Invalidated(reason)` | The ad ended before it was shown: `Expired`, `Rejected`, `Destroyed`, `CreativeFailed(code)`. |
| `NotLoaded` | `show()` before `load()` finished. The ad stays as it was. |
| `ContentPaused` | `show(container)` on an overlay while the content of its `VideoAds` is paused. The ad stays ready: show it once the content plays. |
| `ContainerTooSmall` | The ad would have to shrink below 50 % to fit. `minimumWidthPx` and `minimumHeightPx` are the smallest container that holds it, its padding and your insets included. The ad stays ready. |
| `PresenterUnavailable` | Nothing on screen can hold the ad right now: the Activity for a linear ad is stopped, finishing, destroyed or in picture-in-picture; or the container for an overlay or pause ad is not attached to a window, not shown, has no area, is not on screen in its window (a pager's neighbour page), refuses a child (a `ScrollView` that already has one, a `ListView`), or its Activity is stopped, finishing or destroyed, or the app is in the background. Nothing is spent and the ad stays ready. |
| `WrongPresentation` | `show(activity)` on an overlay or pause ad, `show(container)` or `dismiss()` on a linear ad. |

## `NoFillReason`

| Reason | Meaning |
| - | - |
| `NoAdMatch` | No campaign matched the content, the viewer or the moment |
| `FrequencyCap` | The viewer reached the frequency cap set for your app. Only when a cap is configured |
| `ContentUnsafe` | The content is not brand safe |
| `GeoRestricted` | Not served in this country |
| `FormatNotEnabled` | The format is not enabled for your app, or not for the `labels` this `VideoAds` sends |
| `TemporaryError` | Our side; ask again later |
| `AlreadyUsed` | The SDK's own: the answer was an ad you had just shown or rejected |
| `Unknown` | The server gave no reason |
| `Other(raw)` | A reason this SDK version does not know, passed through unchanged |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.