Skip to main content

Recommendations API

Recommendations API

For programmatic access to content recommendations with custom UI:

import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementRecommendations
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementRecommendationsConfig
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementRecommendationsURLConfig
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementRecommendationsPlatformConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.adplacement.interfaces.core.TeadsAdPlacement
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import com.outbrain.OBSDK.Entities.OBRecommendation
import com.outbrain.OBSDK.Entities.OBRecommendationsResponse
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.TextView

class ContentActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {

private var recommendationsPlacement: TeadsAdPlacementRecommendations? = null
private lateinit var binding: ActivityRecommendationsBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRecommendationsBinding.inflate(layoutInflater)
setContentView(binding.root)

loadRecommendations()
}

private fun loadRecommendations() {
// Create configuration — pass a URL or platform request config (see below)
val config = TeadsAdPlacementRecommendationsConfig(
widgetId = "SDK_1", // Your widget ID
urlConfig = TeadsAdPlacementRecommendationsURLConfig(
articleUrl = Uri.parse("https://yoursite.com/article")
)
)

// Create placement
recommendationsPlacement = TeadsAdPlacementRecommendations(
config, // Placement config
this // Event delegate
)

// Fetch recommendations
lifecycleScope.launch {
try {
val recommendations = recommendationsPlacement?.loadAdSuspend()
recommendations?.let { displayRecommendations(it) }
} catch (e: Exception) {
Log.e("TeadsSDK", "Failed to load recommendations: ${e.message}")
}
}
}

private fun displayRecommendations(recommendations: OBRecommendationsResponse) {
binding.recommendationsContainer.removeAllViews()

if (recommendations.all.isNotEmpty()) {
recommendations.all.forEach { recommendations ->
val recommendationView = createRecommendationView(recommendations)
binding.recommendationsContainer.addView(recommendationView)
}
}
}

private fun createRecommendationView(recommendation: OBRecommendation): View {
val binding = RecommendationItemBinding.inflate(LayoutInflater.from(this))

binding.recommendationTitle.text = recommendation.content
binding.recommendationSource.text = recommendation.sourceName

// Load image (using your preferred image loading library)
recommendation.thumbnail?.url?.let { imageUrl ->
// E.g. Glide.with(this).load(imageUrl).into(binding.recommendationImage)
}

// Handle click
binding.root.setOnClickListener {
TeadsAdPlacementRecommendations.getUrl(recommendation)?.let { url ->
// Open URL in browser
}
}

// Configure viewability tracking — required for accurate impression reporting
TeadsAdPlacementRecommendations.configureViewabilityPerListingFor(
binding.root as ViewGroup,
recommendation
)

return binding.root
}

override fun onPlacementEvent(
placement: TeadsAdPlacement<*, *>,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
// Listen the ad lifecycle events
}
}

Viewability tracking

warning

Viewability tracking is required for accurate impression reporting. Publishers who skip this step will see incorrect viewability metrics in their dashboard.

Call TeadsAdPlacementRecommendations.configureViewabilityPerListingFor() for each recommendation view as you add it to the layout. Pass the ViewGroup container that wraps the recommendation content and the corresponding OBRecommendation object:

TeadsAdPlacementRecommendations.configureViewabilityPerListingFor(
recContainer, // ViewGroup holding the recommendation content
recommendation
)

The SDK monitors viewability automatically once configured and fires the TeadsAdPlacementEventName.VIEWED event when the IAB viewability threshold is met.

AdChoices compliance

warning

AdChoices compliance is required. Omitting the widget icon click handler or the per-item disclosure icon violates Teads ad serving policies.

Widget AdChoices icon

Your recommendations widget header must include a clickable AdChoices icon (ImageView). The SDK ships the icon asset — use @drawable/teads_ic_adchoices directly in your layout:

<ImageView
android:id="@+id/rec_ad_choices_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/teads_ic_adchoices" />

When tapped, open the URL returned by TeadsAdPlacementRecommendations.getAdChoicesURL() in a browser or Custom Tab:

adChoicesIcon.setOnClickListener {
val url = TeadsAdPlacementRecommendations.getAdChoicesURL()
// Open url in browser or Custom Tab
}

RTB disclosure icon (paid recommendations)

Paid recommendations require a per-item disclosure icon overlaid on the thumbnail. In your XML layout, wrap the recommendation ImageView in a FrameLayout and add a second ImageView for the disclosure icon. The disclosure icon must be at least 25dp × 25dp:

<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<!-- Your existing recommendation thumbnail ImageView -->
<ImageView
android:id="@+id/rec_image_view"
android:layout_width="..."
android:layout_height="..." />

<!-- Disclosure icon — minimum 25dp × 25dp -->
<ImageView
android:id="@+id/rec_disclosure_image_view"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="top|end"
android:visibility="gone" />
</FrameLayout>

In code, check isPaid() && shouldDisplayDisclosureIcon() — if true, load the icon and set a click handler; otherwise keep the view GONE:

if (recommendation.isPaid() && recommendation.shouldDisplayDisclosureIcon()) {
disclosureImageView.visibility = View.VISIBLE
// Load icon — e.g. Glide.with(this).load(recommendation.getDisclosure()?.iconUrl).into(disclosureImageView)
disclosureImageView.setOnClickListener {
// Open articleUrl in browser or Custom Tab
}
} else {
disclosureImageView.visibility = View.GONE
}
tip

To verify your implementation, enable RTB test mode so paid recommendations always appear in responses:

TeadsAdPlacementRecommendations.setTestRTB(true)

Remember to remove this call before releasing to production.

Request configuration

TeadsAdPlacementRecommendationsConfig takes a widgetId, a request config (URL or platform), an optional widgetIndex (default 0), and an optional obPubImp. Choose one of the two request configs:

URL requestTeadsAdPlacementRecommendationsURLConfig:

ParameterTypeDescription
articleUrlUriURL of the article for which recommendations are fetched
externalIdString?Optional external identifier

Platform requestTeadsAdPlacementRecommendationsPlatformConfig:

ParameterTypeDescription
bundleUrlString?App bundle URL
portalUrlString?Portal URL
contentUrlString?Content URL
langString?Content language
psubString?Publisher subdomain / section identifier
// Platform request variant
val config = TeadsAdPlacementRecommendationsConfig(
widgetId = "SDK_1",
platformConfig = TeadsAdPlacementRecommendationsPlatformConfig(
bundleUrl = "https://yourapp.example",
contentUrl = "https://yoursite.com/article",
lang = "en"
)
)
warning

The single-argument TeadsAdPlacementRecommendationsConfig(articleUrl, widgetId) constructor is deprecated. Use the widgetId + URL/platform request config constructors shown above.