Sponsored links
Editorially promoted links returned outside the engine's ranking — what they are for, and how to present them honestly.
A sponsored link is curated, not ranked. An administrator decided that a query about "expenses" should surface the expenses policy page, whatever the index would have ranked first. They come back from a separate call, and they belong in a separate area of the interface.
const links = await client.api.query.sponsoredLinks(app.sponsoredLinks, query);Concept — outside the ranking
These results do not come from the search engine's relevance computation. They are matched by rules on the query and returned as-is, which is exactly why they must not be mixed into the ranked list — a user cannot tell the difference, and the ranked list stops meaning what it says.
Fetching them alongside a search
Two independent calls, run together, rendered apart.
import { createAtomicClient } from "@sinequa/atomic";
import type { LinkResult, Query } from "@sinequa/atomic";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
});
await client.initializeConfig();
await client.auth.login();
// The service is named in the configuration, and may be absent entirely.
const app = await client.api.app.get();
const service = app.sponsoredLinks;
export async function searchWithPromoted(query: Query) {
// Independent calls: run them together rather than in sequence.
const [result, links] = await Promise.all([
client.api.query.search(query),
promoted(query),
]);
return { result, links };
}
async function promoted(query: Query): Promise<LinkResult[]> {
// No service configured means the feature is not in use — not an error.
if (!service) return [];
try {
return await client.api.query.sponsoredLinks(service, query);
} catch {
// A promoted block is decoration: never let it fail the search.
return [];
}
}import { ChangeDetectionStrategy, Component, inject, input, resource } from "@angular/core";
import type { Query } from "@sinequa/atomic";
import { SINEQUA } from "./sinequa";
@Component({
selector: "app-sponsored",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (links.value()?.length) {
<!-- Labelled, and visually separate from the ranked results. -->
<aside aria-label="Suggested links">
<ul>
@for (link of links.value() ?? []; track link.id) {
<li>
<a [href]="link.url" [title]="link.tooltip">{{ link.title }}</a>
@if (link.summary) {
<p>{{ link.summary }}</p>
}
</li>
}
</ul>
</aside>
}
`,
})
export class SponsoredComponent {
private readonly client = inject(SINEQUA);
readonly query = input.required<Query>();
/** From `app.sponsoredLinks`; empty when the feature is not configured. */
readonly service = input<string>("");
readonly links = resource({
params: () => ({ service: this.service(), query: this.query() }),
loader: ({ params }) => {
if (!params.service) return Promise.resolve([]);
// Decoration: a failure here must not surface as a search error.
return this.client.api.query
.sponsoredLinks(params.service, params.query)
.catch(() => []);
},
});
}import type { LinkResult, Query } from "@sinequa/atomic";
import { useEffect, useState } from "react";
import { ready, sinequa } from "./sinequa";
export function Sponsored({ service, query }: { service: string; query: Query }) {
const [links, setLinks] = useState<LinkResult[]>([]);
useEffect(() => {
// No service configured means the feature is not in use — not an error.
if (!service) return;
let active = true;
ready
.then(() => sinequa.api.query.sponsoredLinks(service, query))
// Decoration: a failure here must not surface as a search error.
.catch(() => [] as LinkResult[])
.then((next) => active && setLinks(next));
return () => {
active = false;
};
}, [service, query]);
if (links.length === 0) return null;
return (
// Labelled, and visually separate from the ranked results.
<aside aria-label="Suggested links">
<ul>
{links.map((link) => (
<li key={link.id}>
<a href={link.url} title={link.tooltip}>
{link.title}
</a>
{link.summary && <p>{link.summary}</p>}
</li>
))}
</ul>
</aside>
);
}What a link carries
Prop
Type