For developers

iOS 16+ Swift 6

iOS SDK integration

Connect Melvad to your iOS app, configure ad placements, and control loading and display from Swift. Follow the shared integration flow, then choose an example for each supported format.

This guide covers Melvad iOS SDK 1.0.2. Add the binary package through Swift Package Manager or download the complete distribution below.

Other integrations: Cookie syncing OpenRTB formats

1. Prepare your app

  1. Register with Melvad and create an iOS app inventory. Set its Bundle ID and the URL of its actual App Store page in the app settings.
  2. Create placements for the required formats. Get inventoryID and placementID from the placement settings or tag. Use the appropriate placement for each format.
  3. Get your numeric clientID from the Melvad team. The SDK uses https://pub.supplier.melvad.com as its production endpoint by default. Add the SDK from GitHub in the next step; you can override the endpoint for testing.

You need iOS 16+ and Xcode with Swift 6 support. Examples use UIKit. In SwiftUI, host the ad UIView through UIViewRepresentable and retain the session owner in a coordinator or screen model.

The SDK's Bundle ID and App Store URL must match the app in your inventory. App identity for ad selection is configured in the inventory settings.

2. Add the SDK

  1. In Xcode, open File → Add Package Dependencies.
  2. Enter the package URL: https://github.com/Melvad/ios-ads-sdk.
  3. Select Exact Version: 1.0.2 and add both products to your app target: MelvadAdsCore and MelvadAdsRuntime.
  4. Swift Package Manager downloads the device and simulator XCFrameworks with their resources. Prebid is included; do not add a separate second copy.

To use a local package, clone the SDK repository and select its root directory containing Package.swift through Add Local…. Prebuilt XCFrameworks are in the Frameworks folder. Preserve the directory structure, resources and licenses.

For an offline package, download MelvadAdsSDK 1.0.2, unpack it and add the directory containing Package.swift through Add Local…. Keep all binaries and resource bundles together. SHA-256 checksums are available with the release.

MelvadAdsTracking is optional. Add this product only when your app needs ATT-authorized advertising identifiers; see privacy configuration.

See the SDK license for terms of use.

3. Configure requests

Create a configuration for each route: .display for banners and native ads, .video for video and rewarded ads. Pass the values supplied by Melvad and your app's App Store URL to this function.

The default production endpoint is https://pub.supplier.melvad.com; you can omit supplierURL. Set it explicitly for testing. When constructing SupplierConfiguration directly, the same default applies to baseURL.

import UIKit
import WebKit
import MelvadAdsCore
import MelvadAdsRuntime

enum IntegrationError: Error {
    case missingBundleID, missingUserAgent
}

@MainActor
func makeConfiguration(
    supplierURL: URL = SupplierConfiguration.productionBaseURL,
    clientID: Int64,
    appStoreURL: URL,
    route: SupplierConfiguration.Route,
    identityProvider: (any AdvertisingIdentityProvider)? = nil
) async throws -> SupplierConfiguration {
    guard let bundleID = Bundle.main.bundleIdentifier else {
        throw IntegrationError.missingBundleID
    }
    let webView = WKWebView(frame: .zero)
    guard let userAgent = try await webView.evaluateJavaScript(
        "navigator.userAgent"
    ) as? String, !userAgent.isEmpty else {
        throw IntegrationError.missingUserAgent
    }
    return try SupplierConfiguration(
        baseURL: supplierURL,
        clientID: clientID,
        bundleID: bundleID,
        storeURL: appStoreURL,
        userAgent: userAgent,
        language: Locale.preferredLanguages.first,
        route: route,
        identityProvider: identityProvider
    )
}

supplierURL is the base URL without /get, /video, query parameters or the client identifier. The SDK appends /get/{clientID} or /video/{clientID}. Use HTTPS; this integration does not require disabling ATS.

Below, displayConfiguration and videoConfiguration are results of this function with the corresponding route. Call it with try await from @MainActor and handle configuration errors.

4. Pass user consent

Your app manages its CMP and any ATT request. The SDK does not display the system prompt. Create a PrivacyContext from the user's actual settings; keep unknown values unknown.

By default, MelvadAdsCore and MelvadAdsRuntime do not read IDFA or IDFV and do not require your app to link AdSupport or AppTrackingTransparency for SDK requests.

import MelvadAdsCore

@MainActor
func makePrivacy(
    gdprApplies: Bool?,
    consentString: String?,
    personalizationAllowed: Bool,
    globalPrivacyControl: Bool
) -> PrivacyContext {
    PrivacyContext(
        personalizationAllowed: personalizationAllowed,
        gdprApplies: gdprApplies,
        consentString: consentString,
        globalPrivacyControl: globalPrivacyControl
    )
}

To opt in to identifier collection, add the MelvadAdsTracking product and pass SystemAdvertisingIdentityProvider() when creating a configuration:

import MelvadAdsTracking

// Inside a @MainActor method, after your app has handled consent:
let configuration = try await makeConfiguration(
    clientID: clientID,
    appStoreURL: appStoreURL,
    route: .display,
    identityProvider: SystemAdvertisingIdentityProvider()
)

The provider refreshes ATT status before each request. It collects identifiers only when ATT is authorized, personalizationAllowed is true, gdprApplies is explicitly false and globalPrivacyControl is false. With denied, restricted or not-determined ATT, neither IDFA nor IDFV is sent. IDFV is not a replacement for denied ATT.

If your app requests ATT, provide NSUserTrackingUsageDescription and use Apple's API. Keep the SDK privacy manifests in your build and review the app privacy report for your configured demand partners.

Requests work without either identifier. After consent changes, call updatePrivacy: the current load is cancelled and the prepared creative is removed. Your app starts the next load explicitly.

5. Create an ad slot owner

The shared flow is load → ready → show. For fullscreen, you can use present(from:) instead of show(). Loading alone does not display the ad. All API calls and callbacks run on MainActor.

The following app-level helper wraps the actual SDK API. It retains the session, handles clicks, and invokes onReady only after loading, so a regular hide() does not immediately show the ad again.

Full AdSlotController.swift code
import UIKit
import MelvadAdsCore
import MelvadAdsRuntime

@MainActor
final class AdSlotController: NSObject, MelvadAdSessionDelegate {
    let session: MelvadAdSession
    var onReady: ((MelvadAdSession) -> Void)?
    var onUnavailable: (() -> Void)?
    var onError: ((NSError) -> Void)?
    var onEvent: ((String) -> Void)?
    private var awaitingLoad = false

    init(configuration: SupplierConfiguration, privacy: PrivacyContext) {
        session = MelvadAdSession(configuration: configuration)
        super.init()
        session.updatePrivacy(privacy)
        session.delegate = self
    }

    func load(in slot: UIView, inventoryID: String,
              placementID: String, mode: String? = nil) {
        slot.isHidden = false
        slot.superview?.layoutIfNeeded()
        guard slot.window != nil,
              slot.bounds.width >= 1, slot.bounds.height >= 1 else {
            onError?(NSError(domain: "MelvadIntegration", code: 1,
                userInfo: [NSLocalizedDescriptionKey:
                    "Attach the ad slot to a window and set its size before loading."]))
            return
        }
        awaitingLoad = true
        session.load(in: slot, inventoryID: inventoryID,
                     placementID: placementID, mode: mode,
                     width: Int(slot.bounds.width),
                     height: Int(slot.bounds.height))
    }

    func adSession(_ session: MelvadAdSession,
                   didChange state: MelvadAdSessionState) {
        switch state {
        case .ready where awaitingLoad:
            awaitingLoad = false
            onReady?(session)
        case .noFill, .destroyed:
            awaitingLoad = false
            onUnavailable?()
        case .idle, .failed:
            awaitingLoad = false
        default:
            break
        }
    }

    func adSession(_ session: MelvadAdSession, didFail error: NSError) {
        onError?(error)
    }

    func adSession(_ session: MelvadAdSession, didReceiveEvent event: String) {
        onEvent?(event)
    }

    func adSession(_ session: MelvadAdSession, didClick url: URL) {
        UIApplication.shared.open(url)
    }
}

Add this class to your project. Retain the ad slot owners as properties of your UIViewController: the SDK holds its delegate weakly. Use one owner per placement.

// Properties of your UIViewController, not local variables:
private var displayAd: AdSlotController?
private var fullscreenAd: AdSlotController?
private var videoAd: AdSlotController?

In the following examples, privacy is your PrivacyContext, identifiers come from Melvad, and slot is a sized UIView already attached to a window. Load after layout, for example once in viewDidAppear. Do not load on every viewDidLayoutSubviews.

Supported formats

FormatRouteModeDisplay
Banner / MREC.displaynilshow()
Fullscreen banner.display"fullscreen"present(from:)
Adhesion / expandable adhesion.displaynilshow()
Native image / native video.displaynilshow()
Inread / infeed / inslide.video"inread" / "infeed" / "inslide"show()
Fullscreen video.video"fullscreen"show() / present(from:)
Rewarded.video"rewarded"present(from:)
Pre- / mid- / post-roll.videonilMelvadAdBreakController

The server selects the template and creative from the placement settings. mode specifies presentation behavior; it does not turn a banner placement into native or video inventory. Example dimensions are UIKit points, not physical pixels.

6. Banner and MREC

Use displayConfiguration and a banner placement. Size the slot according to your placement settings, for example 320 × 50 for a banner or 300 × 250 for MREC. The container must not clip the creative.

// Inside your UIViewController, create the banner slot once:
let slot = UIView()
slot.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(slot)
NSLayoutConstraint.activate([
    slot.widthAnchor.constraint(equalToConstant: 320),
    slot.heightAnchor.constraint(equalToConstant: 50),
    slot.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    slot.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
// Retain this view and load after the screen appears and layout completes.

After the screen appears and layout completes, load an ad into this slot:

// Inside a method of your UIViewController:
displayAd?.session.destroy()
let ad = AdSlotController(configuration: displayConfiguration, privacy: privacy)
displayAd = ad
ad.onReady = { $0.show() }
ad.onUnavailable = { [weak slot] in slot?.isHidden = true }
ad.onError = { [weak slot] error in
    slot?.isHidden = true
    print(error.domain, error.code, error.localizedDescription)
}
ad.load(in: slot, inventoryID: inventoryID, placementID: placementID)

On noFill, remove the empty slot and let the app continue. An arrangedSubview works well in a UIStackView; update cell heights yourself in a table or collection view.

7. Fullscreen banner

Create a display placement with a fullscreen template. Prepare the ad in a UIView on the current screen with positive width and height. After .ready, present it at an appropriate UI transition.

// Prepare a fullscreen banner using displayConfiguration:
fullscreenAd?.session.destroy()
let ad = AdSlotController(configuration: displayConfiguration, privacy: privacy)
fullscreenAd = ad
ad.onError = { error in
    print(error.domain, error.code, error.localizedDescription)
}
ad.load(in: slot, inventoryID: inventoryID,
        placementID: placementID, mode: "fullscreen")
// A method of your UIViewController; call at an appropriate UI transition:
func showPreparedFullscreen() {
    guard let session = fullscreenAd?.session,
          session.state == .ready,
          view.window != nil,
          presentedViewController == nil else { return }
    do {
        try session.present(from: self)
    } catch {
        print(error.localizedDescription)
    }
}

Present in the same scene as the original container. Only one fullscreen ad can occupy a scene at a time. Closing the ad destroys its session; create a new session for the next ad.

8. Adhesion and expandable adhesion

Use .display and a placement configured for adhesion or expandable adhesion. No separate mode: "adhesion" value is needed. The SDK anchors the banner at the bottom safe area; your app reserves space for content above it.

// Use a placement configured for adhesion or expandable adhesion.
displayAd?.session.destroy()
let ad = AdSlotController(configuration: displayConfiguration, privacy: privacy)
displayAd = ad
ad.onReady = { $0.show() }
ad.onUnavailable = { [weak slot] in slot?.isHidden = true }
ad.onError = { [weak slot] error in
    slot?.isHidden = true
    print(error.localizedDescription)
}
ad.load(in: slot, inventoryID: inventoryID, placementID: adhesionPlacementID)

Fullscreen expansion is available when allowed by the supplier settings and MRAID creative. It requires user interaction. Closing an expanded ad returns to the same banner; closing the banner itself ends the session.

The supported MRAID commands cover opening links, visibility, geometry, expand, close and playVideo. In the current profile, resize() expands to fullscreen; two-part expansion and forced orientation are not supported.

9. Native image and native video

Both formats use displayConfiguration. Select a placement configured for native image or native video. The SDK renders the card from the response; you do not need to fetch and assemble individual assets.

// Use the placement ID for native image OR native video.
displayAd?.session.destroy()
let ad = AdSlotController(configuration: displayConfiguration, privacy: privacy)
displayAd = ad
ad.onReady = { $0.show() }
ad.onUnavailable = { [weak nativeSlot] in nativeSlot?.isHidden = true }
ad.onError = { [weak nativeSlot] error in
    nativeSlot?.isHidden = true
    print(error.localizedDescription)
}
ad.load(in: nativeSlot, inventoryID: inventoryID, placementID: nativePlacementID)

Place nativeSlot within a feed or article, with regular content before and after it. Give the card enough height; the SDK example uses 460 pt. Call show() for native ads; present(from:) is not supported.

Native video starts muted after display is authorized and at least 50% is visible. It pauses offscreen and in the background. Remove the slot on completion. The current native-video path supports one VAST InLine with a linear MP4 creative; VAST wrapper chains are not supported in this path.

10. Inread, infeed, inslide and fullscreen video

Use videoConfiguration and a video placement. This method loads the ad and enables display when it is ready. Choose one of the four modes.

ModeBehavior
.inreadIn an article; playback while at least 50% visible.
.infeedIn a feed; starts after one second at 50% visibility, pauses offscreen.
.inslideA sliding unit: at the bottom on iPhone, on the right on iPad.
.fullscreenFullscreen video with SDK controls.
// A method of your UIViewController:
func loadVideo(in slot: UIView, configuration: SupplierConfiguration,
               privacy: PrivacyContext, inventoryID: String,
               placementID: String, mode: MelvadVideoMode) {
    videoAd?.session.destroy()
    let ad = AdSlotController(configuration: configuration, privacy: privacy)
    videoAd = ad
    ad.onReady = { $0.show() }
    ad.onUnavailable = { [weak slot] in slot?.isHidden = true }
    ad.onError = { [weak slot] error in
        slot?.isHidden = true
        print(error.localizedDescription)
    }
    ad.load(in: slot, inventoryID: inventoryID, placementID: placementID,
            mode: mode.supplierValue)
}

// Call from your screen after layout; choose one mode for this placement:
// loadVideo(in: slot, configuration: videoConfiguration, privacy: privacy,
//           inventoryID: inventoryID, placementID: placementID, mode: .inread)
// Other modes: .infeed, .inslide, .fullscreen

For inread/infeed, prepare a slot in your article or feed, typically at 16:9. For inslide and fullscreen, the original UIView must also be attached to a window. For fullscreen, the screen must not have another presented modal. To separate preparation from display, omit show() from onReady and call it later while the session is .ready.

11. Rewarded video

Use videoConfiguration, a dedicated rewarded placement, and the string mode: "rewarded". Rewarded is not a case of MelvadVideoMode. Let the user opt in before starting the ad.

// Call after the user opts in to watch a rewarded ad:
fullscreenAd?.session.destroy()
let ad = AdSlotController(configuration: videoConfiguration, privacy: privacy)
fullscreenAd = ad
ad.onReady = { [weak self] _ in self?.showPreparedFullscreen() }
ad.onUnavailable = { print("Rewarded ad unavailable or closed") }
ad.onError = { error in print(error.localizedDescription) }
ad.onEvent = { event in
    if event == "AdVideoComplete" {
        print("Video completed; this is not a verified reward")
    }
}
ad.load(in: slot, inventoryID: inventoryID,
        placementID: rewardedPlacementID, mode: "rewarded")

Reward verification. AdVideoComplete reports video completion only. The current package does not provide a ready SSV contract for server-side reward verification. Do not grant a verified reward based on this callback; agree on the server contract with Melvad before launching rewarded monetization.

12. Pre-roll, mid-roll and post-roll

Your external player plays the main content. MelvadAdBreakController pauses it and displays ads. For AVPlayer, add this adapter; implement the same two methods for another content player.

import AVFoundation
import MelvadAdsRuntime

@MainActor
final class ContentAdapter: MelvadContentPlayback {
    let player: AVPlayer
    init(player: AVPlayer) { self.player = player }
    func playContent() { player.play() }
    func pauseContent() { player.pause() }
}

Add an ad UIView over the content player's area. After layout, create the controller with videoConfiguration and an instream placement. This example runs a pre-roll, one mid-roll at 30 seconds, and a post-roll.

// Properties of your UIViewController:
private var contentAdapter: ContentAdapter?
private var adBreaks: MelvadAdBreakController?

// Inside a method, after laying out the ad overlay inside the content player:
adBreaks?.destroy()
let adapter = ContentAdapter(player: contentPlayer)
contentAdapter = adapter
let breaks = MelvadAdBreakController(
    content: adapter,
    container: adOverlay,
    configuration: videoConfiguration,
    inventoryID: inventoryID,
    placementID: instreamPlacementID,
    midrollTime: 30,
    privacy: privacy
)
adBreaks = breaks
breaks.onEvent = { print($0) }
breaks.start()

Connect your player's periodic time callback and the end event for the current content item. With AVPlayer, use addPeriodicTimeObserver and AVPlayerItem.didPlayToEndTimeNotification. Forward events on MainActor and remove observers when releasing the screen.

// Forward player callbacks on MainActor:
adBreaks?.contentTimeDidChange(contentPlayer.currentTime().seconds)
// Call the line above on periodic playback updates, not just once.

// When the current content item finishes:
adBreaks?.contentDidFinish()

// When the user's privacy settings change:
adBreaks?.updatePrivacy(updatedPrivacy)

// Before leaving the screen; also remove the player's time/end observers:
adBreaks?.destroy()
adBreaks = nil
contentAdapter = nil

Pre-/mid-/post-roll are insertion points, not mode values. The controller makes a separate video request for each break using the same placement. No-fill or an error returns control to the content; post-roll does not restart it. Create a new controller for a new content item or replay.

13. Lifecycle and integration checks

// Pause and resume the SAME inline creative:
displayAd?.session.hide()
// Later, when the slot should be visible again:
displayAd?.session.show()

// After a privacy change, discard the prepared creative:
displayAd?.session.updatePrivacy(updatedPrivacy)
// Start a new load explicitly with the new privacy context.

// Before removing a slot, reusing a cell, or leaving the screen:
displayAd?.session.destroy()
displayAd = nil
videoAd?.session.destroy()
videoAd = nil
fullscreenAd?.session.destroy()
fullscreenAd = nil
  • destroy() is terminal. Create a new session afterward. A new load on a live session replaces its previous load and creative.
  • A prepared creative expires after at most 120 seconds; Prebid may set a shorter lifetime. Load a new ad after expiry.
  • show() after an inline hide() uses the same creative. Closing a modal video destroys its session.
  • The SDK owns impression, billing and VAST trackers. Do not send duplicate tracking requests from your app.
  • On a physical device, check each format, clicks, closing, foreground return, scrolling, cell reuse and consent changes.
ResultWhat to check
noFillNormal absence of an ad. Check placement status, format and demand settings; collapse the empty slot.
failedRecord the NSError domain/code. Check the HTTPS endpoint, clientID, inventory/placement, network and app configuration.
Ready, but nothing is visibleWas show/present called? Is the container attached, sized and visible? Is the app in the foreground?
Fullscreen will not openUse the same scene, no competing modal or fullscreen ad, and a non-expired creative. Native requires inline display.

The SDK does not automatically retry an auction. Schedule retries with a frequency limit; do not start an endless load() loop from an error callback. Passing tests with fixture responses does not guarantee live demand for a placement.

For help, contact support@melvad.com with the SDK version, iOS version, format, inventory/placement and reproduction steps. Remove tokens, device identifiers and consent data before sharing logs.

14. Native Open Measurement

Version 1.0.2 includes native OMSDK_Melvad 1.6.12. The runtime manages the measurement session and ad/media events for display and VAST video, including pre-roll, mid-roll and post-roll. No additional OMID initialization is required in your app.

Do not add a second web OMID Service/Session Client or manually send measurement events for the same creative. VAST verification resources come from AdVerifications; playback without usable verification resources continues without an OMID session. This integration does not imply IAB certification.

15. SDK logging and errors

Logging is off by default. Set the minimum level to debug, info, warning or error and provide a handler:

import MelvadAdsCore

MelvadLogger.configure(minimumLevel: .debug) { entry in
    print("[\(entry.subsystem)] \(entry.message)")
}

// Disable SDK logging:
MelvadLogger.configure()

The Swift handler runs on the emitting thread; dispatch UI work to MainActor. Objective-C apps can use MelvadDiagnostics.configure(minimumLevel:handler:), whose handler runs on MainActor. SDK records contain lifecycle/status metadata rather than creative markup, consent values or device identifiers.

In adSession(_:didFail:), inspect NSError.localizedDescription, domain, code and NSUnderlyingErrorKey. The underlying network error is retained. noFill remains a normal result, not an error.

Back to the start of the guide

Android 10+ · API 29+ · SDK 1.0.2

Android SDK integration

Add Melvad to your Android app, choose ad placements and manage loading, display and lifecycle from Kotlin.

This guide uses the public binary distribution of Melvad Android Ads SDK 1.0.2. The SDK includes its renderers; Gradle installs the required native dependencies.

Native Open Measurement (OMID) is included in the SDK and requires no additional configuration in your app.

Other integrations: iOS SDK · Cookie syncing · OpenRTB formats

1. Prepare your app

Register an Android app inventory in Melvad and configure its package name and Google Play page. Create a placement for each required format. Get its key in the form inventory_id#placement_id#client_id; the final part is your numeric client ID. The SDK reads the app package name and constructs its Google Play URL automatically.

Requires Android 10/API 29+, compile SDK 37+, a Kotlin 2.2.10-compatible compiler and Android System WebView with WebMessageListener support. Use a JDK supported by your Android Gradle Plugin. Create views and call SDK methods on the Android main thread.

2. Install the binary SDK

Merge this repository into dependencyResolutionManagement.repositories in settings.gradle.kts, preserving your existing repositories:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://raw.githubusercontent.com/Melvad/android-ads-sdk/1.0.2/maven")
            content { includeGroup("com.melvad") }
        }
    }
}

Add this dependency to your app module’s build.gradle.kts and set minSdk = 29 or later:

dependencies {
    implementation("com.melvad:android-sdk:1.0.2")
}

Gradle resolves the AAR, native Prebid Mobile and the other runtime dependencies. You do not need a GitLab account or a source build. Adding only a local AAR does not install its transitive dependencies. See the binary release and checksums and the SDK license.

3. Pass resolved consent

Resolve the publisher’s CMP decision before calling loadAd. Pass the actual GDPR applicability and TC string; the SDK does not infer permission to send an ad request. Call this helper with your resolved values:

fun privacySnapshot(
    gdprApplies: Boolean,
    tcString: String,
    advertisingIdAllowed: Boolean
): MelvadPrivacy = MelvadPrivacy(
    gdprApplies = gdprApplies,
    tcString = tcString,
    limitAdTracking = !advertisingIdAllowed
)

limitAdTracking controls advertising ID use. It does not replace the app’s decision to permit an ad request. Advertising ID use is limited by default. The SDK uses an advertising ID only when the app and platform settings permit it; unavailable IDs do not block loading. Use a fresh privacy snapshot and a new load after consent changes.

4. Create and retain an ad view

Create one MelvadSdk(activity) client for the app or screen. The helper below attaches a view, waits for layout, then starts one request. Pass your client, Activity, slot container, placement key and resolved privacy snapshot. Retain the returned view until it is released.

import android.app.Activity
import android.util.Log
import android.view.ViewGroup
import com.melvad.sdk.*

fun attachAd(
    sdk: MelvadSdk,
    activity: Activity,
    host: ViewGroup,
    placementKey: String,
    privacy: MelvadPrivacy,
    format: MelvadAdFormat = MelvadAdFormat.BANNER,
    videoOptions: MelvadVideoOptions = MelvadVideoOptions(),
    instream: MelvadInstreamOptions? = null
): MelvadAdView {
    val view = sdk.createAdView(activity)
    val fullSize = format in setOf(
        MelvadAdFormat.INTERSTITIAL, MelvadAdFormat.FULLSCREEN,
        MelvadAdFormat.REWARDED, MelvadAdFormat.INSTREAM
    )
    host.addView(view, ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        if (fullSize) ViewGroup.LayoutParams.MATCH_PARENT
        else ViewGroup.LayoutParams.WRAP_CONTENT
    ))
    view.listener = object : MelvadAdListener {
        override fun onLoaded(view: MelvadAdView) {
            Log.d("Melvad", "Creative loaded")
        }
        override fun onImpression(view: MelvadAdView) {
            Log.d("Melvad", "Visible impression")
        }
        override fun onError(view: MelvadAdView, error: MelvadAdError) {
            Log.e("Melvad", "Ad result: " + error.code)
        }
    }
    view.post {
        if (view.parent === host && view.state != MelvadAdState.DESTROYED) {
            view.loadAd(MelvadAdRequest(
                placement = MelvadPlacement.parse(placementKey),
                format = format,
                privacy = privacy,
                videoOptions = videoOptions,
                instream = instream
            ))
        }
    }
    return view
}

onLoaded means the creative is ready. onImpression is a separate visible impression. The SDK handles tracking; do not duplicate impression, billing or VAST requests in your app.

Supported formats

FormatPlacement and hostBehaviour
BANNER22 / 23Fixed-size / inline banner
EXPANDABLE22, intsl=1Inline banner with fullscreen expansion
INTERSTITIAL20, intsl=1Fullscreen HTML banner
ADHESION21Bottom dock outside scrolling content
NATIVE10 / 11Inline image or video card
VIDEOVideo placementInline video host
INREADVideo placementCollapsing inline host, WRAP_CONTENT
INSLIDEVideo placementBottom dock outside scrolling content
INFEEDVideo placementPersistent inline card
INSTREAMVideo placementOverlay covering the content player
FULLSCREENVideo placementWindow-sized video host; no reward
REWARDEDVideo placementWindow-sized video host; completion reward

5. Fixed-size and expandable banners

For BANNER, call attachAd with its default format. For an expandable inline banner, pass MelvadAdFormat.EXPANDABLE and use a matching placement. The fixed banner dimensions come from the placement settings. Other banner formats use the renderer’s template behaviour.

Expandable views require an Activity context. Observe onExpanded/onCollapsed and isExpanded; call collapse() when appropriate. Template 22 returns to its inline slot without a second load. Closing an expanded adhesion closes that placement.

6. Fullscreen banners, video and rewarded

Use a window-sized host with system-bar and display-cutout insets. INTERSTITIAL is a fullscreen HTML banner; FULLSCREEN is video. Use REWARDED only after the user chooses to watch a rewarded ad. This example creates and releases a full-window dialog:

fun showFullscreenAd(
    sdk: MelvadSdk,
    activity: Activity,
    placementKey: String,
    privacy: MelvadPrivacy,
    format: MelvadAdFormat,
    onReward: () -> Unit = {}
): android.app.Dialog {
    require(format in setOf(
        MelvadAdFormat.INTERSTITIAL,
        MelvadAdFormat.FULLSCREEN,
        MelvadAdFormat.REWARDED
    ))
    val dialog = android.app.Dialog(
        activity, android.R.style.Theme_Material_Light_NoActionBar
    )
    val host = android.widget.FrameLayout(activity)
    host.fitsSystemWindows = true
    dialog.setContentView(host)
    val view = attachAd(sdk, activity, host, placementKey, privacy, format)
    view.listener = object : MelvadAdListener {
        override fun onStateChanged(view: MelvadAdView, state: MelvadAdState) {
            if (state == MelvadAdState.CLOSED) dialog.dismiss()
        }
        override fun onError(view: MelvadAdView, error: MelvadAdError) {
            dialog.dismiss()
        }
        override fun onRewardEarned(view: MelvadAdView) { onReward() }
    }
    dialog.setOnDismissListener { view.destroy() }
    dialog.show()
    dialog.window?.setLayout(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT
    )
    return dialog
}

Apply a reward only in onRewardEarned. The SDK emits it once after a visible, completed rewarded video; early close, no-fill and errors do not grant a reward. Retain the dialog and dismiss it when its Activity is destroyed.

7. Adhesion and inslide

Place the bottom dock outside scrolling content. The publisher’s root must account for system bars, the keyboard and other overlays. This helper uses ADHESION by default; pass INSLIDE with a video placement for sliding video. The renderer supplies its close control and reports the required native height.

fun loadDockedAd(
    sdk: MelvadSdk,
    activity: Activity,
    root: android.widget.FrameLayout,
    placementKey: String,
    privacy: MelvadPrivacy,
    format: MelvadAdFormat = MelvadAdFormat.ADHESION
): MelvadAdView {
    require(format in setOf(MelvadAdFormat.ADHESION, MelvadAdFormat.INSLIDE))
    val dock = android.widget.FrameLayout(activity)
    root.addView(dock, android.widget.FrameLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.WRAP_CONTENT,
        android.view.Gravity.BOTTOM
    ))
    return attachAd(sdk, activity, dock, placementKey, privacy, format)
}

8. Native image and native video

Use NATIVE for matching image-card or video-card placements. The placement selects template 10 or 11 and its theme. These are HTML cards rendered by the SDK, rather than assets to assemble into Android Views. Keep the host width valid and allow the SDK to update its height.

fun loadNativeCard(
    sdk: MelvadSdk, activity: Activity, host: ViewGroup,
    placementKey: String, privacy: MelvadPrivacy
): MelvadAdView = attachAd(
    sdk, activity, host, placementKey, privacy, MelvadAdFormat.NATIVE
)

9. Inread, infeed and inline video

Use an inline WRAP_CONTENT host for INREAD, INFEED and VIDEO. Inread opens when visible; infeed retains its card/poster and supports retry. Inslide uses the bottom-dock example above. Native visibility and app lifecycle control playback.

fun loadInlineVideo(
    sdk: MelvadSdk, activity: Activity, host: ViewGroup,
    placementKey: String, privacy: MelvadPrivacy,
    format: MelvadAdFormat, posterUrl: String? = null
): MelvadAdView {
    require(format in setOf(
        MelvadAdFormat.VIDEO, MelvadAdFormat.INREAD, MelvadAdFormat.INFEED
    ))
    return attachAd(sdk, activity, host, placementKey, privacy, format,
        videoOptions = MelvadVideoOptions(
            posterUrl = posterUrl,
            refreshSeconds = 0,
            closeButton = format == MelvadAdFormat.INFEED
        )
    )
}

For a recoverable infeed opportunity, observe onVideoError/videoState and call retryVideo() on an explicit retry action. Use setVideoMuted(false) after a user audio action. HTTPS poster URLs are optional. Zero refreshSeconds requests a single ad for inread/inslide.

10. Instream: pre-roll, mid-roll and post-roll

Place an INSTREAM ad view over your content player, filling its area. Your app owns content playback. Connect both pause/resume callbacks and feed the content timeline. The example schedules pre-roll, a mid-roll at 30 seconds and post-roll:

fun loadInstreamAd(
    sdk: MelvadSdk, activity: Activity, overlay: ViewGroup,
    placementKey: String, privacy: MelvadPrivacy,
    pauseContent: () -> Unit, resumeContent: () -> Unit
): MelvadAdView {
    val options = MelvadInstreamOptions(breaks = listOf(
        MelvadAdBreak("pre", MelvadAdBreakPosition.PREROLL),
        MelvadAdBreak("mid-30", MelvadAdBreakPosition.MIDROLL, offsetMillis = 30_000),
        MelvadAdBreak("post", MelvadAdBreakPosition.POSTROLL)
    ))
    return attachAd(sdk, activity, overlay, placementKey, privacy,
        MelvadAdFormat.INSTREAM, instream = options
    ).apply {
        listener = object : MelvadAdListener {
            override fun onContentPauseRequested(view: MelvadAdView) { pauseContent() }
            override fun onContentResumeRequested(view: MelvadAdView) { resumeContent() }
            override fun onVideoError(view: MelvadAdView, error: MelvadAdError) {
                Log.e("Melvad", "Video opportunity: " + error.code)
            }
        }
    }
}

After obtaining adView from the helper, forward the actual player events on the main thread. A position uses milliseconds, not seconds:

// When prepared content is started by the user:
adView.startContent()
// On every content-player time/seek/playback update, using milliseconds:
adView.updateContent(positionMillis, durationMillis, isPlaying)
// When the current content item ends:
adView.updateContent(durationMillis, durationMillis, false, ended = true)

For a VMAP schedule, use MelvadInstreamOptions(vmapUrl = yourHttpsVmapUrl) instead of breaks. Do not pass both. A break may specify its own MelvadPlacement; otherwise it uses the enclosing request’s placement. No-fill or a recoverable video error returns control to content and preserves later breaks.

11. Lifecycle and troubleshooting

A view attached to a ViewTreeLifecycleOwner follows that lifecycle. In a plain Activity or custom host, forward pause() and resume(). Call destroy() before permanently removing a slot, reusing a cell or leaving the screen. Destroy is terminal; create a new view afterward. The following complete Activity owns one banner and makes no automatic requests:

import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.view.ViewGroup
import android.widget.FrameLayout
import com.melvad.sdk.*

class AdsActivity : Activity() {
    private lateinit var sdk: MelvadSdk
    private lateinit var host: FrameLayout
    private var ad: MelvadAdView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sdk = MelvadSdk(this)
        host = FrameLayout(this)
        setContentView(host)
    }

    // Call from your CMP callback after the app permits an ad request.
    fun showBanner(placementKey: String, privacy: MelvadPrivacy) {
        ad?.destroy()
        host.removeAllViews()
        val view = sdk.createAdView(this)
        ad = view
        host.addView(view, FrameLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        ))
        view.listener = object : MelvadAdListener {
            override fun onLoaded(view: MelvadAdView) {
                Log.d("Melvad", "Creative loaded")
            }
            override fun onImpression(view: MelvadAdView) {
                Log.d("Melvad", "Visible impression")
            }
            override fun onError(view: MelvadAdView, error: MelvadAdError) {
                Log.e("Melvad", "Ad result: " + error.code)
            }
        }
        view.post {
            if (ad === view) view.loadAd(MelvadAdRequest(
                placement = MelvadPlacement.parse(placementKey),
                format = MelvadAdFormat.BANNER,
                privacy = privacy
            ))
        }
    }

    override fun onPause() { ad?.pause(); super.onPause() }
    override fun onResume() { super.onResume(); ad?.resume() }
    override fun onDestroy() { ad?.destroy(); ad = null; super.onDestroy() }
}

Call showBanner(placementKey, privacy) from your app’s resolved consent flow. In a Fragment, destroy the retained view in onDestroyView(). In Compose, create it in AndroidView.factory and release it in onRelease; do not load on every recomposition.

ResultWhat to check
NO_FILLNo eligible ad. Check placement status, format and demand; collapse the empty slot or schedule a controlled retry.
TIMEOUTCheck connectivity and configured load timeout. Avoid immediate retry loops.
RENDERER_UNAVAILABLEUpdate Android System WebView and check WebMessageListener support.
RENDERER_ERROR / WEBVIEW_TERMINATEDCheck the selected format and host. Destroy the failed view and create a new one for a later load.
Loaded but not visibleCheck that the slot is attached, sized, visible and in the foreground. Match fullscreen/docked placements to the correct native host.

Before publishing your app, test live placements on physical devices: scrolling, background/foreground, clicks, dismissal, rotation, consent changes and rewarded completion. For help, send the SDK version, Android/WebView versions, format and reproduction steps to support@melvad.com. Remove private identifiers and consent data from shared logs.

Back to the start of the Android guide

Incoming cookie sync requests

Example URL:

https://sync.dmp.melvad.com/match/0?ssp_id=0&user_id={user_id}&rt={REDIRECT_TO_ENCODE_URL}
&gdpr=${GDPR}&gdpr_consent=${GDPR_CONSENT_XXXXX}&sub_field1=&sub_field2=&sub_field3=&sub_field4=&sub_field5=

where

  • {user_id} - user identificator in SSP
  • {REDIRECT_TO_ENCODE_URL} - URL to which one you want to redirect the request (302 redirect), must be passed in encode
  • ${GDPR} - the value 0 or 1 according to documentation CMP API v2
  • ${GDPR_CONSENT_XXXX} - user consent to the processing of his data according to documentation CMP API v2
  • sub_field1, sub_field2, sub_field3, sub_field4, sub_field5 - optional parameters to which you can pass your value if you need to pass it in the redirect URL
  • %7B%7Bsub_field1%7D%7D, %7B%7Bsub_field2%7D%7D, %7B%7Bsub_field3%7D%7D, %7B%7Bsub_field4%7D%7D, %7B%7Bsub_field5%7D%7D - optional macros that are placed in the redirect URL, if it is necessary to forward the value from the corresponding parameters "sub_field1..5"

Supported ad formats in the OpenRTB standard (version OpenRTB 2.5)

Banner ad

Fullscreen ad

Request example

{
   "id": "o37du34ajfj34h",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "pdsu2389fdfj34",
         "secure": 1,
         "banner": {
            "w": 375,
            "h": 667,
            "format": [
               {
                  "w": 375,
                  "h": 667
               },
               {
                  "w": 320,
                  "h": 480
               }
            ],
            "api": [
               5
            ],
            "mimes": [
               "image/png",
               "image/jpeg",
               "image/gif",
               "text/html",
			   "text/plain"
            ],
            "btype": [
               1,
               3,
               4
            ],
            "battr": [
               3,
               4,
               5
            ]
         },
         "instl": 1
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr"
      },
      "cat": [
         "IAB9"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Note: the "api" attribute is optional, but only for displaying ads in the mobile app

Response example

Expanding ad

Request example

{
   "id": "o37du34ajfj34h",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "pdsu2389fdfj34",
         "secure": 1,
         "banner": {
            "w": 320,
            "h": 480,
            "format": [
               {
                  "w": 320,
                  "h": 480
               }
            ],
            "api": [
               5
            ],
            "expdir": [
               5
            ],
            "mimes": [
               "image/png",
               "image/jpeg",
               "image/gif",
               "text/html",
			   "text/plain"
            ],
            "btype": [
               1,
               3,
               4
            ]
         },
         "instl": 1
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr"
      },
      "cat": [
         "IAB9"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Note:

  • the expansion direction is passed to the "expdir" attribute, if it contains the value 5, then the "instl" attribute with the value 1 must also be passed
  • the "api" attribute is optional, but only for displaying ads in the mobile app

Response example

Adhesive ad

Request example

{
   "id": "o37du34ajfj34h",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "pdsu2389fdfj34",
         "secure": 1,
         "banner": {
            "w": 414,
            "h": 50,
            "format": [
               {
                  "w": 414,
                  "h": 50
               }
            ],
            "api": [
               5
            ],
            "mimes": [
               "image/png",
               "image/jpeg",
               "image/gif",
               "text/html",
			   "text/plain"
            ],
            "btype": [
               1,
               3,
               4
            ],
            "battr": [
               3,
               4,
               5
            ]
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr"
      },
      "cat": [
         "IAB9"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Response example

Adhesive fullscreen ad

Request example

{
   "id": "o37du34ajfj34h",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "pdsu2389fdfj34",
         "secure": 1,
         "banner": {
            "w": 414,
            "h": 50,
            "format": [
               {
                  "w": 414,
                  "h": 50
               }
            ],
            "api": [
               5
            ],
            "mimes": [
               "image/png",
               "image/jpeg",
               "image/gif",
               "text/html",
			   "text/plain"
            ],
            "btype": [
               1,
               3,
               4
            ]
         },
         "instl": 1
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr"
      },
      "cat": [
         "IAB9"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Response example

Fixed size ad

Request example

{
   "id": "o37du34ajfj34h",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "pdsu2389fdfj34",
         "secure": 1,
         "banner": {
            "w": 728,
            "h": 90,
            "format": [
               {
                  "w": 728,
                  "h": 90
               },
               {
                  "w": 468,
                  "h": 60
               }
            ],
            "api": [
               5
            ],
            "mimes": [
               "image/png",
               "image/jpeg",
               "image/gif",
               "text/html",
			   "text/plain",
               "application/javascript",
               "text/javascript"
            ],
            "btype": [
               1,
               4
            ],
            "battr": [
               3,
               4,
               5
            ]
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr"
      },
      "cat": [
         "IAB9"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Note: the "api" attribute is optional, but only for displaying ads in the mobile app

Response example

Video ad

Skippable in-stream ad with companion ad

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "video": {
            "w": 640,
            "h": 360,
            "minduration": 5,
            "maxduration": 60,
            "minbitrate": 500,
            "maxbitrate": 6000,
            "protocols": [
               3
            ],
            "api": [
               2
            ],
            "mimes": [
               "video/mp4",
               "video/webm",
               "video/mpeg",
               "application/javascript"
            ],
            "linearity": 1,
            "delivery": [
               2
            ],
            "startdelay": 0,
            "placement": 1,
            "companionad": [
               {
                  "id": "1",
                  "format": [
                     {
                        "w": 300,
                        "h": 250
                     }
                  ],
                  "w": 300,
                  "h": 250,
                  "btype": [
                     1,
                     3,
                     4
                  ]
               }
            ],
            "companiontype": [
               1
            ]
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344"
      },
      "cat": [
         "IAB12",
         "IAB17-12"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Note: the "startdelay" attribute can be -2, -1, 0 or any number greater than 1

Response example

Skippable in-stream ad with sequential ad chain

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "video": {
            "w": 640,
            "h": 360,
            "minduration": 5,
            "maxduration": 60,
            "minbitrate": 500,
            "maxbitrate": 6000,
            "sequence": 3,
            "protocols": [
               3
            ],
            "api": [
               2
            ],
            "mimes": [
               "video/mp4",
               "video/webm",
               "video/mpeg",
               "application/javascript"
            ],
            "linearity": 1,
            "delivery": [
               2
            ],
            "startdelay": 0,
            "placement": 1
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344"
      },
      "cat": [
         "IAB12",
         "IAB17-12"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Note:

  • the "sequence" attribute can be a value of 2 or 3
  • the "startdelay" attribute can be -2, -1, 0, or any number greater than 1

Response example

Skippable in-stream ad

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "video": {
            "w": 640,
            "h": 360,
            "minduration": 5,
            "maxduration": 60,
            "minbitrate": 500,
            "maxbitrate": 6000,
            "protocols": [
               3,
               6
            ],
            "api": [
               2
            ],
            "mimes": [
               "video/mp4",
               "video/webm",
               "video/mpeg",
               "application/javascript"
            ],
            "linearity": 1,
            "delivery": [
               2
            ],
            "startdelay": 0,
            "placement": 1
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344"
      },
      "cat": [
         "IAB12",
         "IAB17-12"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Note: the "startdelay" attribute can be -2, -1, 0 or any number greater than 1

Response example

Rewarded out-stream ad

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "video": {
            "w": 640,
            "h": 360,
            "minduration": 5,
            "maxduration": 60,
            "minbitrate": 500,
            "maxbitrate": 6000,
            "protocols": [
               3
            ],
            "api": [
               2
            ],
            "mimes": [
               "video/mp4",
               "video/webm",
               "video/mpeg",
               "application/javascript"
            ],
            "linearity": 1,
            "delivery": [
               2
            ],
            "placement": 4,
            "ext": {
               "rewarded": 1
            }
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles" 
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr" 
      },
      "cat": [
         "IAB9" 
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Note: the "placement" attribute is optional

Response example

Fullscreen out-stream ad

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "video": {
            "w": 640,
            "h": 360,
            "minduration": 5,
            "maxduration": 60,
            "minbitrate": 500,
            "maxbitrate": 6000,
            "protocols": [
               3
            ],
            "api": [
               2
            ],
            "mimes": [
               "video/mp4",
               "video/webm",
               "video/mpeg",
               "application/javascript"
            ],
            "linearity": 1,
            "delivery": [
               2
            ],
            "placement": 5
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles" 
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr" 
      },
      "cat": [
         "IAB9" 
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Response example

Out-stream ad

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "video": {
            "w": 640,
            "h": 360,
            "minduration": 5,
            "maxduration": 60,
            "minbitrate": 500,
            "maxbitrate": 6000,
            "protocols": [
               3
            ],
            "api": [
               2
            ],
            "mimes": [
               "video/mp4",
               "video/webm",
               "video/mpeg",
               "application/javascript"
            ],
            "linearity": 1,
            "delivery": [
               2
            ],
            "placement": 4
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344"
      },
      "cat": [
         "IAB12",
         "IAB17-12"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Note: the "placement" attribute can be a value of 3 or 4

Response example

Nonlinear ad

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB"
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "video": {
            "w": 320,
            "h": 50,
            "protocols": [
               3
            ],
            "mimes": [
               "image/gif",
               "image/jpeg",
               "image/png"
            ],
            "linearity": 2
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles"
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344"
      },
      "cat": [
         "IAB12",
         "IAB17-12"
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Response example

Native ad

Mobile app in feed

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB" 
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "native": {
            "request": "{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":25}},{\"id\":2,\"required\":1,\"img\":{\"type\":1,\"wmin\":100,\"hmin\":100,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":3,\"required\":1,\"img\":{\"type\":3,\"wmin\":1200,\"hmin\":627,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":5,\"data\":{\"type\":3,\"len\":5}},{\"id\":6,\"data\":{\"type\":4,\"len\":11}},{\"id\":7,\"data\":{\"type\":12,\"len\":15}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}",
            "ver": "1.2" 
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles" 
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr" 
      },
      "cat": [
         "IAB9" 
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Response example

Mobile app in feed with video

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB" 
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "native": {
            "request": "{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":25}},{\"id\":2,\"required\":1,\"img\":{\"type\":1,\"wmin\":100,\"hmin\":100,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":3,\"required\":1,\"img\":{\"type\":3,\"wmin\":1200,\"hmin\":627,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":4,\"required\":1,\"video\":{\"mimes\":[\"video\/mp4\",\"video\/webm\",\"video\/mpeg\"],\"protocols\":[3]}},{\"id\":5,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":3,\"len\":5}},{\"id\":7,\"data\":{\"type\":4,\"len\":11}},{\"id\":8,\"data\":{\"type\":12,\"len\":15}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}",
            "ver": "1.2" 
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles" 
      }
   },
   "app": {
      "id": "9fu48u347ru34o",
      "name": "Angry Birds 2",
      "bundle": "com.rovio.baba",
      "storeurl": "https://apps.apple.com/app/angry-birds-2/id880047117",
      "publisher": {
         "id": "p-8e23ywepo3rr" 
      },
      "cat": [
         "IAB9" 
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c"
   }
}

Response example

In content

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB" 
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "native": {
            "request": "{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":25}},{\"id\":2,\"img\":{\"type\":1,\"wmin\":100,\"hmin\":100,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":3,\"required\":1,\"img\":{\"type\":3,\"wmin\":1200,\"hmin\":627,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":5,\"required\":1,\"data\":{\"type\":1,\"len\":25}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}",
            "ver": "1.2" 
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles" 
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344" 
      },
      "cat": [
         "IAB12",
         "IAB17-12" 
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c" 
   }
}

Response example

In content with video

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB" 
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "native": {
            "request": "{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":25}},{\"id\":2,\"required\":1,\"img\":{\"type\":1,\"wmin\":100,\"hmin\":100,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":3,\"required\":1,\"img\":{\"type\":3,\"wmin\":1200,\"hmin\":627,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":4,\"required\":1,\"video\":{\"mimes\":[\"video\/mp4\",\"video\/webm\",\"video\/mpeg\"],\"protocols\":[3]}},{\"id\":5,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"required\":1,\"data\":{\"type\":1,\"len\":25}},{\"id\":7,\"data\":{\"type\":12,\"len\":15}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}",
            "ver": "1.2" 
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles" 
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344" 
      },
      "cat": [
         "IAB12",
         "IAB17-12" 
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c" 
   }
}

Response example

Text and image

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB" 
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "native": {
            "request": "{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":25}},{\"id\":2,\"data\":{\"type\":12,\"len\":15}},{\"id\":3,\"required\":1,\"img\":{\"type\":3,\"wmin\":1200,\"hmin\":627,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":4,\"data\":{\"type\":2,\"len\":90}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}",
            "ver": "1.2" 
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles" 
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344" 
      },
      "cat": [
         "IAB12",
         "IAB17-12" 
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c" 
   }
}

Response example

Text and video

Request example

{
   "id": "o37934lssd34fgd",
   "cur": [
      "USD",
      "RUB" 
   ],
   "at": 2,
   "imp": [
      {
         "id": "1",
         "bidfloor": 0.25,
         "bidfloorcur": "USD",
         "tagid": "ierui234fdfdf",
         "secure": 1,
         "native": {
            "request": "{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":25}},{\"id\":2,\"data\":{\"type\":12,\"len\":15}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"wmin\":100,\"hmin\":100,\"mimes\":[\"image\/png\",\"image\/jpeg\",\"image\/gif\",\"text\/html\"]}},{\"id\":4,\"data\":{\"type\":3,\"len\":5}},{\"id\":5,\"required\":1,\"video\":{\"mimes\":[\"video\/mp4\",\"video\/webm\",\"video\/mpeg\"],\"protocols\":[3]}},{\"id\":6,\"data\":{\"type\":2,\"len\":90}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}",
            "ver": "1.2" 
         }
      }
   ],
   "device": {
      "w": 375,
      "h": 667,
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/112.0.5615.46 Mobile/15E148 Safari/604.1",
      "ip": "69.75.122.146",
      "ifa": "460384F3-D3B0-47CA-8090-F120F270A8B7",
      "language": "en",
      "geo": {
         "country": "USA",
         "region": "CA",
         "city": "Los Angeles" 
      }
   },
   "site": {
      "id": "9fu48u347ru34o",
      "domain": "tass.ru",
      "page": "https://tass.ru/sport/4237913",
      "publisher": {
         "id": "p-93dj34di3344" 
      },
      "cat": [
         "IAB12",
         "IAB17-12" 
      ]
   },
   "user": {
      "id": "d92a55e6-3eda-44c4-a448-14830a4c6838",
      "buyeruid": "213ba1a2425be71ec5080b1d64b8450c" 
   }
}

Response example

For technical support, you can write to the address support@melvad.com and describe the nature of the problem to be solved and provide related information to improve understanding of the problem. We will use our best efforts to respond to such requests in a timely manner, but we cannot guarantee that the issue will be resolved.