Skip to main content
A publisher’s video player sits inside an article page. For each article, it should play the videos that fit the story and are safe for the advertisers on the page. This guide builds that on Mixpeek:
  1. Videos sync from your S3 bucket and are embedded and described.
  2. Articles arrive through the API with their text.
  3. A labeling retriever writes IAB categories, entities, sentiment and a brand-safety rating onto every article and video at ingest.
  4. A playlist retriever takes an article and returns ranked, brand-safe videos.
  5. Your player calls it behind a feature flag, keeps its own playlist as the fallback, and sends viewer events back.
Every resource below can live in one manifest, so dev, staging and production run the same configuration (environment branching).

Prerequisites

  • An S3 bucket of video files and an IAM role Mixpeek can assume, set up as in AWS S3.
  • A Mixpeek API key and a namespace. Requests below use $MP_API_KEY and $MP_NAMESPACE.

1. Bring in the video library

Create a videos bucket whose schema holds the file and the metadata your player filters on:
Then sync your S3 prefix into it. Upload each video with its metadata as x-amz-meta-* headers, and map those keys to the bucket fields with metadata sources, as shown in Object metadata. A continuous sync with a 60-second poll picks up a new upload within a minute. For the existing catalog, run the backfill at a lower priority than new uploads. See Backfill and one-at-a-time uploads.

2. Embed and describe the videos

A collection with multimodal_extractor v2 embeds each video with Gemini Embedding 2. With run_video_description, Gemini also watches and listens to the video and writes a text description, which the labeling step reads:
field_passthrough copies the bucket fields onto each document, so the playlist can filter on them. See Multimodal extractor for every parameter.

3. Send articles

Create an articles bucket with article_id, title, body and publisher_id, and an article-signals collection on it with the same extractor and "input_mappings": { "text": "body" }. Articles and videos are then embedded in the same vector space, so an article can search the videos directly. Send each article as an object, one blob per field, with its text in body. Strip the page markup before you send it: the text extractor reads body as plain text.
Submit the new object in a batch, and pass a webhook_url or subscribe to collection.documents.batch_completed to learn when its document is ready (Webhooks).

4. Label articles and videos at ingest

The signals come from a retriever that runs once on every new document, as a retriever enrichment. Its first stage selects the document being labeled. Its second is an llm_enrich stage that returns the labels as structured output:
Attach it to both collections. Each run writes the label fields back onto the document it labeled:
Put the full IAB 3.1 list in the prompt, so the model copies a category rather than inventing one. Articles and videos get the same field names, so the playlist can compare them.

5. Build the playlist retriever

The playlist retriever takes an article’s document_id and searches the videos with that article’s own embedding, using input_mode: "document". No new inference runs at query time. The search filter keeps brand-unsafe videos out and applies the publisher’s rights: its own videos or the shared library, minus channels it has excluded (filter example). Pass excluded_channels as [] when a publisher excludes none.
To rank on more than vector similarity, add stages between search and limit. A document_enrich stage can attach the article’s entities and IAB categories to each candidate. A code_execution stage can then score shared entities and IAB depth, and sort_attribute can order by that score.

6. Call it from the player, behind a flag

Execute the retriever with the article’s document id. Keep your current playlist as the fallback: use it when the flag is off, the call fails or times out, the run is degraded, or fewer videos come back than the player needs.
Read documents, check status, and log warnings. A run that skips a stage answers 200 with status: "degraded", so a short list is not always a lack of matches. Keep execution_id: the next step needs it.

7. Send viewer and editor signals back

Record what happened to each video as an interaction, with the execution_id and retriever_id of the run that chose it: impression when it is shown, play, long_view when watched to the end, and skip. During a pilot, record editors’ verdicts on a match as positive_feedback or negative_feedback. These rows land in your namespace’s system collections, joined to the query that produced them. Use them to build an evaluation set, compare ranking configurations, and roll a new one out to a share of traffic with auto-tune rollout.

Next steps

Manifests

Declare the buckets, collections and retrievers above in one file.

Batch ingestion at scale

Size the backfill and keep new uploads ahead of it.

Filters

Every operator for rights, recency and brand-safety rules.

Reward signals

How plays and completions feed ranking.