Merino
The Merino component provides two clients for interacting with the Merino service:
CuratedRecommendationsClient— Fetches personalized content recommendations, powering features like Firefox’s New Tab page.SuggestClient— Fetches search suggestions, powering features like Firefox’s address bar.
Prerequisites
Ensure that Viaduct is initialized during application startup, as it is used for making network requests.
The SuggestClient additionally requires the OHTTP channel to be configured before making requests:
import mozilla.appservices.viaduct.OhttpConfig
import mozilla.appservices.viaduct.configureOhttpChannel
configureOhttpChannel(
channel = "merino",
config = OhttpConfig(
relayUrl = "https://ohttp-merino.mozilla.fastly-edge.com",
gatewayHost = "ohttp-gateway-merino.services.mozilla.com",
),
)
// Configure the OHTTP channel before using SuggestClient
let config = OhttpConfig(
relayUrl: "https://ohttp-merino.mozilla.fastly-edge.com",
gatewayHost: "ohttp-gateway-merino.services.mozilla.com"
)
try configureOhttpChannel(channelId: "merino", config: config)
Async
Both clients are synchronous — calling them directly will block the current thread. Consumers should wrap calls in an async implementation.
Curated Recommendations Client
Fetches personalized content recommendations from the Merino Service. Merino Curated Recommendations API Docs
The API for the CuratedRecommendationsClient can be found in the Mozilla Rust components Kotlin API Reference and Swift API Reference.
Importing the Client
import mozilla.appservices.merino.CuratedRecommendationsClient
import mozilla.appservices.merino.CuratedRecommendationsRequest
import mozilla.appservices.merino.CuratedRecommendationsResponse
import mozilla.appservices.merino.CuratedRecommendationsApiException
import MozillaAppServices
Initializing the Curated Recommendations Client
The CuratedRecommendationsClient is initialized using a CuratedRecommendationsConfig object. This includes a required userAgentHeader and an optional baseHost. If baseHost is not provided, the client will default to the production host.
val config = CuratedRecommendationsConfig(
baseHost = "https://merino.services.mozilla.com"
userAgentHeader = "Mozilla/5.0"
)
val client = CuratedRecommendationsClient(config)
let config = CuratedRecommendationsConfig(
baseHost: "https://merino.services.mozilla.com"
userAgentHeader: "Mozilla/5.0"
)
let client = try CuratedRecommendationsClient(config: config)
Fetching Curated Recommendations
The getCuratedRecommendations() method fetches recommendations based on the provided request parameters.
val request = CuratedRecommendationsRequest(
locale = Locale.EN_US,
region = "US",
count = 4,
topics = listOf("business"),
feeds = listOf("sections")
)
try {
val response: CuratedRecommendationsResponse = client.getCuratedRecommendations(request)
println("Received recommendations: $response")
} catch (e: CuratedRecommendationsError) {
println("Error fetching recommendations: ${e.message}")
}
let request = CuratedRecommendationsRequest(
locale: Locale.en-US,
region: "US",
count: 4,
topics: ["business"],
feeds: ["sections"]
)
do {
let response = try client.getCuratedRecommendations(request: request)
print("Received recommendations: \(response)")
} catch {
print("Error fetching recommendations: \(error)")
}
Data Models
Curated Recommendations Request Model
The CuratedRecommendationsRequest model defines the parameters required to request curated recommendations.
Request Fields
Field |
Type |
Description |
|---|---|---|
|
|
The Firefox installed locale, e.g., |
|
|
(Optional) The country-level region, e.g., |
|
|
(Optional) The maximum number of recommendations to return. Defaults to |
|
|
(Optional) A list of preferred curated topics. |
|
|
(Optional) A list of additional data feeds. Accepted values: |
|
|
(Optional) A list of section settings that the user follows or has blocked. |
|
|
(Optional) The Nimbus New Tab experiment name that the user is enrolled in. Used to run backend experiments independently of Firefox releases. |
|
|
(Optional) The branch name of the Nimbus experiment that the user is in. |
|
|
(Optional, defaults to |
Curated Recommendations Response Model
The CuratedRecommendationsResponse model defines the response format containing recommendations.
Response Fields
Field |
Type |
Description |
|---|---|---|
|
|
The timestamp (in milliseconds) indicating when the recommendations were generated. |
|
|
A list of curated recommendation items. |
|
|
(Optional) A structured set of multiple curated recommendation lists. |
|
|
(Optional) Returned if |
Error Handling
The Curated Recommendations component defines the following error hierarchy:
CuratedRecommendationsApiError: Base errorNetwork(reason: string): Network error while making a request.Other(code: integer (optional), reason: string): Generic error containing an HTTP status code and message.
Handling Errors in Kotlin and Swift
fun fetchCuratedRecommendations() {
try {
val response = client.getCuratedRecommendations(request)
} catch (e: CuratedRecommendationsError.Network) {
// Log and retry after 5 minutes
Log.w("Network error when fetching Curated Recommendations: ${e.reason}")
scheduleRetry(300)
} catch (e: CuratedRecommendationsError.Other) {
when (e.code) {
400 -> Log.e("Bad Request: ${e.reason}")
422 -> Log.e("Validation Error: ${e.reason}")
in 500..599 -> Log.e("Server Error: ${e.reason}")
else -> Log.e("Unexpected Error: ${e.reason}")
}
}
}
func fetchCuratedRecommendations() {
do {
let response = try client.getCuratedRecommendations(request)
} catch CuratedRecommendationsError.Network(let reason) {
// Log and retry after 5 minutes
print("Network error when fetching Curated Recommendations: \(reason)")
scheduleRetry(seconds: 300)
} catch CuratedRecommendationsError.Other(let code, let reason) {
switch code {
case 400:
print("Bad Request: \(reason)")
case 422:
print("Validation Error: \(reason)")
case 500...599:
print("Server Error: \(reason)")
default:
print("Unexpected Error: \(reason)")
}
}
}
Suggest Client
Fetches search suggestions from the Merino suggest endpoint. Merino Suggest API Docs
The API for the SuggestClient can be found in the Mozilla Rust components Kotlin API Reference and Swift API Reference.
Importing the Client
import mozilla.appservices.merino.SuggestClient
import mozilla.appservices.merino.SuggestConfig
import mozilla.appservices.merino.SuggestOptions
import mozilla.appservices.merino.MerinoSuggestApiException
import MozillaAppServices
Initializing the Suggest Client
The SuggestClient is initialized using a SuggestConfig object with an optional baseHost. If not provided, it defaults to the production host.
val config = SuggestConfig(
baseHost = null // defaults to https://merino.services.mozilla.com
)
val client = SuggestClient(config)
let config = SuggestConfig(baseHost: nil) // defaults to https://merino.services.mozilla.com
let client = try SuggestClient(config: config)
Fetching Suggestions
The getSuggestions() method returns a raw JSON string containing the Merino suggest response. Callers are responsible for deserializing the response.
val options = SuggestOptions(
providers = listOf("wikipedia", "adm"),
source = "urlbar",
country = "US",
region = "CA",
city = "San Francisco",
clientVariants = null,
requestType = null,
acceptLanguage = "en-US"
)
try {
val json: String = client.getSuggestions(query = "firefox", options = options)
// parse json as needed
} catch (e: MerinoSuggestApiException) {
println("Error fetching suggestions: ${e.message}")
}
let options = SuggestOptions(
providers: ["wikipedia", "adm"],
source: "urlbar",
country: "US",
region: "CA",
city: "San Francisco",
clientVariants: nil,
requestType: nil,
acceptLanguage: "en-US"
)
do {
let json: String = try client.getSuggestions(query: "firefox", options: options)
// parse json as needed
} catch {
print("Error fetching suggestions: \(error)")
}
Data Models
SuggestConfig
Field |
Type |
Description |
|---|---|---|
|
|
The base host for the Merino endpoint. Defaults to |
SuggestOptions
All fields are optional — omitted fields are not sent to Merino.
Field |
Type |
Description |
|---|---|---|
|
|
List of suggestion providers to query (e.g. |
|
|
Identifier of which part of Firefox the request comes from (e.g. |
|
|
ISO 3166-1 country code (e.g. |
|
|
Subdivision code(s) (e.g. |
|
|
City name (e.g. |
|
|
List of active experiments or rollouts affecting the client’s Suggest experience. An empty list is treated the same as omitting the field. |
|
|
For the AccuWeather provider: |
|
|
The |
Response
getSuggestions() returns the raw JSON response body as a string. The response follows the Merino suggest API schema.
Error Handling
The Suggest component defines the following error hierarchy:
MerinoSuggestApiError: Base errorNetwork(reason: string): A network-level failure (e.g. no connectivity, OHTTP not configured).Other(code: integer (optional), reason: string): An HTTP error or unexpected failure, with an optional status code.
Handling Errors in Kotlin and Swift
fun fetchSuggestions() {
try {
val json = client.getSuggestions(query = "firefox", options = options)
} catch (e: MerinoSuggestApiException.Network) {
// Log and retry
Log.w("Network error when fetching suggestions: ${e.reason}")
} catch (e: MerinoSuggestApiException.Other) {
when (e.code) {
400 -> Log.e("Bad Request: ${e.reason}")
422 -> Log.e("Validation Error: ${e.reason}")
in 500..599 -> Log.e("Server Error: ${e.reason}")
else -> Log.e("Unexpected Error: ${e.reason}")
}
}
}
func fetchSuggestions() {
do {
let json = try client.getSuggestions(query: "firefox", options: options)
} catch MerinoSuggestApiError.network(let reason) {
// Log and retry
print("Network error when fetching suggestions: \(reason)")
} catch MerinoSuggestApiError.other(let code, let reason) {
switch code {
case 400:
print("Bad Request: \(reason)")
case 422:
print("Validation Error: \(reason)")
case 500...599:
print("Server Error: \(reason)")
default:
print("Unexpected Error: \(reason)")
}
}
}