---
title: "Feed lenses: how Open Media orders the Feed"
description: "Raw, Relevant and every shared feed algorithm are small JSON manifests run by one open-source runner. The format, the built-in lenses, and how agents write, preview, save and share one."
canonical: https://api.openmedia.site/docs/feed-lenses
last-updated: 2026-10-27
---

# Feed lenses

Every way of ordering the Open Media Feed is a **lens**: a small JSON manifest, never code. Raw and Relevant are the two built in. People can describe a new lens in plain words in Search, or ask an AI agent to write one, then keep it, share it publicly, or install someone else's.

One open-source runner orders the posts: [src/domain/feedLenses.ts](https://github.com/mihirkavi/open-media/blob/main/src/domain/feedLenses.ts). The app, the REST API and the MCP servers all use it, so an agent's preview matches the person's Feed. The design is [ADR 0017](https://github.com/mihirkavi/open-media/blob/main/docs/adrs/0017-feed-lenses.md).

## The built-in lenses

### Relevant

Newer posts first, lifted when you follow the author or a post matches a topic you chose, with authors and networks spread out. Nothing else is used.

- Ranked by newness 50%, who you follow 30%, your topics 20%
- Spreads authors and networks out

```json
{
  "format": "open-media.feed-lens/1",
  "id": "relevant",
  "name": "Relevant",
  "version": 2,
  "builtIn": true,
  "author": "Open Media",
  "description": "Newer posts first, lifted when you follow the author or a post matches a topic you chose, with authors and networks spread out. Nothing else is used.",
  "permissions": [
    "follows",
    "interests"
  ],
  "filters": {},
  "order": "ranked",
  "objectives": {
    "recency": 0.5,
    "follows": 0.3,
    "interests": 0.2,
    "engagement": 0,
    "diversity": 0.3
  },
  "interests": []
}
```

### Raw

Every eligible post, newest first. Nothing is ranked or hidden by the lens. Raw is always available.

- Newest first

```json
{
  "format": "open-media.feed-lens/1",
  "id": "raw",
  "name": "Raw",
  "version": 1,
  "builtIn": true,
  "author": "Open Media",
  "description": "Every eligible post, newest first. Nothing is ranked or hidden by the lens. Raw is always available.",
  "permissions": [],
  "filters": {},
  "order": "newest",
  "objectives": {
    "recency": 1,
    "follows": 0,
    "interests": 0,
    "engagement": 0,
    "diversity": 0
  },
  "interests": []
}
```

Relevant's recency counts half as much every 12 hours, so older posts still appear in time order rather than all at the bottom.

## What a lens can and cannot read

- Always: each post's text, time, network and kind (video, image, product, service, event).
- Only when its manifest lists the permission: `follows` (who the person follows on Open Media), `interests` (topics the person or the lens chose), `public_engagement` (like and repost counts a network publishes).
- Never, for any lens: Private messages, Exact location, Sensitive-trait inference, KYC documents, Emotional-state prediction. These cannot be written in a manifest; unknown fields are refused.

## Writing a lens

```json
{
  "format": "open-media.feed-lens/1",
  "name": "Cooking videos",
  "permissions": ["follows"],
  "filters": { "kinds": ["video"], "about": ["cooking"], "mute": ["crypto"], "hideSources": ["hive"] },
  "order": "ranked",
  "objectives": { "recency": 0.6, "follows": 0.4, "interests": 0, "engagement": 0, "diversity": 0.3 },
  "interests": []
}
```

Fields: `filters` keep posts (`sources`, `kinds`, `about`, `followedOnly`, `maxAgeHours`) and leave them out (`hideSources`, `hideKinds`, `mute`, `muteAuthors`). `order` is `newest` or `ranked`. `objectives` weigh recency, follows, interests and engagement (normalized), and `diversity` spreads authors out. The full field list is in `GET https://api.openmedia.site/v1/lenses`.

## For agents

Public, no key (`https://api.openmedia.site/open/mcp` or REST):

- `list_feed_lenses` / `GET https://api.openmedia.site/v1/lenses?q=`: the built-in and shared lenses, with rules in words.
- `get_feed_lens` / `GET https://api.openmedia.site/v1/lenses/{id}`.
- `draft_feed_lens` / `POST https://api.openmedia.site/v1/lenses/draft` `{"request": "only videos about cooking, no crypto"}`: plain words to a manifest, with what was and was not understood.
- `preview_feed_lens` / `POST https://api.openmedia.site/v1/lenses/preview` `{"lens": {…}}` or `{"request": "…"}`: the public Feed through that lens, each post with its "why".
- `get_feed` with `lens` / `GET https://api.openmedia.site/v1/feed?lens=relevant`.

The person's account (`https://api.openmedia.site/me/mcp`, bearer token):

- `list_my_feed_lenses`, `save_feed_lens` (`visibility`: private or public), `install_feed_lens` (copy a shared lens), `delete_feed_lens`, and `get_my_feed` (the Feed through a lens with the person's follows).

Show the person a lens's summary and rules before saving it, and ask before sharing it publicly. A saved lens appears in the person's Feed picker on every device; a shared one opens for anyone at `https://openmedia.site/?lens=%3Cid%3E`.

## In the app

There is no lens editor. People write what they want in Search ("only videos from people I follow, no crypto"); Search shows the lens it understood, and choosing it switches the Feed. The Feed's picker shows Relevant, Raw and the lens in use. "Why this?" on every post lists the signals used, the lens's rules and what was never used.
