---
title: "feed.item@1 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/feed.item"
updated: null
---

# feed.item@1 · OpenVibe.Services

HTML: [feed.item@1 · OpenVibe.Services](https://openvibe.services/docs/contracts/feed.item)

# FeedItem `feed.item@1`

Generated at 2026-11-13 06:25:48 UTC from [`openvibe-contracts v0.419.0`](https://github.com/OpenVibers/OpenVibe.Contracts/tree/v0.419.0) and [`openvibe-sdk v0.161.0`](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.161.0).

- Version: 1.0.0
- Owner: network
- Visibility: public
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/feed/item.v1.json`](/docs/contracts/feed.item.json)

feed.item@1: one card of OpenVibe's one feed, served by OpenVibe.Network (GET /api/v1/feed and GET /api/frame/feed; OpenVibe.Community's Pulse reads the same feed). Every card names the service it came from, a stable id, its own link, and only public or follower-visible activity. Kinds: post, pic, clip, video, live, stream, rundown, quest, member, chat, event, wiki, update, work, deal, news, report, poll, game, meme, paste, episode, space, avatar, banner, follow, site.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `id` | string | yes | Stable across feeds and restarts: f:<service>:<type>:<id> for a source item and t:<site>:<url> for a trending card. | - `minLength 1`<br>- `maxLength 500` |
| `kind` | enum | yes | What happened. live is a channel on air now (a go-live that ended is stream); stream is a past broadcast, including a watched-together summary; work is one grouped row of new jobs per hour; report is a number with its trend. | - `one of "post", "pic", "clip", "video", "live", "stream", "rundown", "quest", "member", "chat", "event", "wiki", "update", "work", "deal", "news", "report", "poll", "game", "meme", "paste", "episode", "space", "avatar", "banner", "follow", "site"` |
| `url` | string | yes | The thing itself: the post, stream, job board, report page or wiki page. | - `pattern ^https?://`<br>- `maxLength 2000` |
| `title` | string | yes |  | - `maxLength 300` |
| `text` | string | yes | The source's first 280 characters, on one line; empty when it has none. | - `maxLength 280` |
| `at` | string \| null | yes | When it happened; null only for a trending card. | - `format date-time` |
| `starts_at` | string |  | When a scheduled event begins; present only on event cards. | - `format date-time` |
| `site` | string | yes | The source service: live, community, media-hub, pics, games, quest, chat, today, work, deals, news, report, wiki, blog, tools, network… | - `maxLength 40` |
| `site_name` | string | yes |  | - `maxLength 40` |
| `actor` | object \| null | yes | Who did it; null for AI, system, aggregate and trending cards. |  |
| `subject` | string \| null | yes | The acting person's subject, when one is known; null for AI, system and aggregate cards. The Following filter and For you ranking read it. | - `pattern ^usr_[0-9A-HJKMNP-TV-Z]{26}$` |
| `media` | object \| null | yes | The card's first picture, as a Media thumbnail; null when it has none. |  |
| `counts` | object | yes | The numbers the source knows; each present only when it knows it. | - `no other fields` |
| `counts.views` | integer |  |  | - `minimum 0` |
| `counts.likes` | integer |  |  | - `minimum 0` |
| `counts.comments` | integer |  |  | - `minimum 0` |
| `counts.watchers` | integer |  | People who watched a stream (peak for a past broadcast). | - `minimum 0` |
| `counts.people` | integer |  | A count a report card is about (people online, jobs, stories). | - `minimum 0` |
| `live_now` | boolean |  | Present on live and stream cards: true only while the channel is on air. |  |
| `trend` | object |  | Present on report cards: which way the number moved and by how much. | - `no other fields` |
| `trend.direction` | enum | yes |  | - `one of "up", "down", "flat"` |
| `trend.pct` | number |  |  | - `minimum 0` |
| `trend.previous` | integer |  |  | - `minimum 0` |
| `group` | object |  | A run of the same kind folded into one card (two or more items). | - `no other fields` |
| `group.count` | integer | yes |  | - `minimum 2` |
| `group.items` | array of object | yes |  | - `minItems 2`<br>- `items: no other fields` |
| `group.items[].id` | string | yes |  |  |
| `group.items[].url` | string | yes |  | - `pattern ^https://` |
| `group.items[].title` | string | yes |  |  |
| `group.items[].media` | object \| null | yes |  |  |
| `label` | enum |  | Present only when the card needs one. | - `one of "Trending", "AI", "System", "Live"` |
| `visibility` | enum |  | Present only for an item returned to a follower or its author; never public. | - `one of "followers"` |

## Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

 Valid: avatar

~~~~
{
  "id": "f:example:avatar:1",
  "kind": "avatar",
  "url": "https://openvibe.network/@alex",
  "title": "Alex changed their picture",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "network",
  "site_name": "Network",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: banner

~~~~
{
  "id": "f:example:banner:1",
  "kind": "banner",
  "url": "https://openvibe.network/@alex",
  "title": "Alex changed their banner",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "network",
  "site_name": "Network",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: chat

~~~~
{
  "id": "f:example:chat:1",
  "kind": "chat",
  "url": "https://openvibe.chat/r/feed",
  "title": "A busy hour in #feed",
  "text": "28 messages",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "chat",
  "site_name": "Chat",
  "actor": null,
  "subject": null,
  "media": null,
  "counts": {
    "people": 9
  }
}
~~~~

Valid: clip

~~~~
{
  "id": "f:example:clip:1",
  "kind": "clip",
  "url": "https://openvibe.live/clip/c1",
  "title": "A clip",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "live",
  "site_name": "Live",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": {
    "thumb": "https://openvibe.media/t/clip.jpg",
    "ratio": 1.777,
    "length": 42
  },
  "counts": {}
}
~~~~

Valid: deal

~~~~
{
  "id": "f:example:deal:1",
  "kind": "deal",
  "url": "https://openvibe.deals/d/example",
  "title": "A deal on a keyboard",
  "text": "20% off",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "deals",
  "site_name": "Deals",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {
    "likes": 3
  }
}
~~~~

Valid: episode

~~~~
{
  "id": "f:example:episode:1",
  "kind": "episode",
  "url": "https://openvibe.live/rundown/e/3005",
  "title": "Rundown: an episode",
  "text": "A logline",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "live",
  "site_name": "Live",
  "actor": null,
  "subject": null,
  "media": null,
  "counts": {}
}
~~~~

Valid: event

~~~~
{
  "id": "f:example:event:1",
  "kind": "event",
  "url": "https://openvibe.today/e/tev_example",
  "title": "Coming up: a stream about feeds",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "today",
  "site_name": "Today",
  "actor": null,
  "subject": null,
  "media": null,
  "counts": {},
  "starts_at": "2026-11-13T18:00:00.000Z"
}
~~~~

Valid: follow

~~~~
{
  "id": "f:example:follow:1",
  "kind": "follow",
  "url": "https://openvibe.live/@alex",
  "title": "Alex followed Maticus",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "network",
  "site_name": "Network",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: game

~~~~
{
  "id": "f:example:game:1",
  "kind": "game",
  "url": "https://openvibe.games/world/example",
  "title": "Unlocked a level in Scraplandia",
  "text": "Level 15",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "games",
  "site_name": "Games",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: live

~~~~
{
  "id": "f:example:live:1",
  "kind": "live",
  "url": "https://openvibe.live/@alex",
  "title": "Alex went live: building a feed",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "live",
  "site_name": "Live",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {
    "watchers": 12
  },
  "live_now": true,
  "label": "Live"
}
~~~~

Valid: member

~~~~
{
  "id": "f:example:member:1",
  "kind": "member",
  "url": "https://openvibe.network/stats",
  "title": "5 people joined today",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "network",
  "site_name": "Network",
  "actor": null,
  "subject": null,
  "media": null,
  "counts": {},
  "label": "System"
}
~~~~

Valid: meme

~~~~
{
  "id": "f:example:meme:1",
  "kind": "meme",
  "url": "https://openvibe.meme/m/example",
  "title": "A meme with receipts",
  "text": "Weaker rulers show mercy.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "meme",
  "site_name": "Meme",
  "actor": null,
  "subject": null,
  "media": null,
  "counts": {},
  "label": "System"
}
~~~~

Valid: news

~~~~
{
  "id": "f:example:news:1",
  "kind": "news",
  "url": "https://openvibe.news/s/example",
  "title": "News: the feed ships",
  "text": "Sourced, not spun.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "news",
  "site_name": "News",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: paste

~~~~
{
  "id": "f:example:paste:1",
  "kind": "paste",
  "url": "https://openvibe.community/p/example",
  "title": "A paste",
  "text": "print(\"hello\")",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "community",
  "site_name": "Community",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {
    "views": 10,
    "likes": 1
  }
}
~~~~

Valid: pic

~~~~
{
  "id": "f:example:pic:1",
  "kind": "pic",
  "url": "https://openvibe.pics/p/pic_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "title": "A picture",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "pics",
  "site_name": "Pics",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": {
    "thumb": "https://openvibe.media/t/example.jpg",
    "ratio": 1.5
  },
  "counts": {}
}
~~~~

Valid: poll

~~~~
{
  "id": "f:example:poll:1",
  "kind": "poll",
  "url": "https://openvibe.community/forum/status/poll",
  "title": "Which feed kind do you open first?",
  "text": "A poll",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "community",
  "site_name": "Community",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {
    "people": 41
  }
}
~~~~

Valid: post

~~~~
{
  "id": "f:example:post:1",
  "kind": "post",
  "url": "https://openvibe.community/forum/status/hello",
  "title": "Hello, network",
  "text": "A first post.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "community",
  "site_name": "Community",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {
    "likes": 4,
    "comments": 2
  }
}
~~~~

Valid: quest

~~~~
{
  "id": "f:example:quest:1",
  "kind": "quest",
  "url": "https://openvibe.quest/",
  "title": "Finished a quest: first broadcast",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "quest",
  "site_name": "Quest",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: report

~~~~
{
  "id": "f:example:report:1",
  "kind": "report",
  "url": "https://openvibe.report/network",
  "title": "Peak today: 1,204 people online at 9 PM",
  "text": "Online peak across the network",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "report",
  "site_name": "Report",
  "actor": null,
  "subject": null,
  "media": null,
  "counts": {
    "people": 1204
  },
  "trend": {
    "direction": "up",
    "pct": 12
  },
  "label": "System"
}
~~~~

Valid: rundown

~~~~
{
  "id": "f:example:rundown:1",
  "kind": "rundown",
  "url": "https://openvibe.live/rundown/e/3005",
  "title": "Rundown: the case for a feed",
  "text": "What the episode was about.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "live",
  "site_name": "Live",
  "actor": null,
  "subject": null,
  "media": {
    "thumb": "https://openvibe.media/t/episode.jpg",
    "ratio": 1.777
  },
  "counts": {},
  "label": "System"
}
~~~~

Valid: site

~~~~
{
  "id": "f:example:site:1",
  "kind": "site",
  "url": "https://openvibe.network/",
  "title": "A new site joined the network",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "network",
  "site_name": "Network",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: space

~~~~
{
  "id": "f:example:space:1",
  "kind": "space",
  "url": "https://openvibe.space/s/example",
  "title": "A new space",
  "text": "A site for a project.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "space",
  "site_name": "Space",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: stream

~~~~
{
  "id": "f:example:stream:1",
  "kind": "stream",
  "url": "https://openvibe.live/@alex",
  "title": "312 people watched Alex tonight",
  "text": "A watched-together stream.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "live",
  "site_name": "Live",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {
    "watchers": 312,
    "views": 410
  },
  "live_now": false
}
~~~~

Valid: update

~~~~
{
  "id": "f:example:update:1",
  "kind": "update",
  "url": "https://openvibe.blog/p/feed",
  "title": "New post on the OpenVibe blog",
  "text": "The feed is one.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "blog",
  "site_name": "Blog",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: video

~~~~
{
  "id": "f:example:video:1",
  "kind": "video",
  "url": "https://openvibe.video/watch?v=example",
  "title": "Recording: an evening",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "live",
  "site_name": "Live",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": {
    "thumb": "https://openvibe.media/t/vod.jpg",
    "ratio": 1.777,
    "length": 3600
  },
  "counts": {}
}
~~~~

Valid: wiki

~~~~
{
  "id": "f:example:wiki:1",
  "kind": "wiki",
  "url": "https://openvibe.wiki/space/page",
  "title": "Wiki: OpenVibe / feed",
  "text": "How the one feed works.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "wiki",
  "site_name": "Wiki",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Valid: work

~~~~
{
  "id": "f:example:work:1",
  "kind": "work",
  "url": "https://openvibe.work/jobs",
  "title": "14 new jobs this hour",
  "text": "Remote: GIS engineer, and more",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "work",
  "site_name": "Work",
  "actor": null,
  "subject": null,
  "media": null,
  "counts": {
    "people": 14
  }
}
~~~~

Rejected: bad-subject

~~~~
{
  "id": "f:example:post:1",
  "kind": "post",
  "url": "https://openvibe.example/reference",
  "title": "An example post",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "example",
  "site_name": "Example",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "alex",
  "media": null,
  "counts": {}
}
~~~~

Rejected: group-too-small

~~~~
{
  "id": "f:example:video:1",
  "kind": "video",
  "url": "https://openvibe.example/reference",
  "title": "An example video",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "example",
  "site_name": "Example",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {},
  "group": {
    "count": 1,
    "items": []
  }
}
~~~~

Rejected: missing-id

~~~~
{
  "kind": "post",
  "url": "https://openvibe.example/reference",
  "title": "An example post",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "example",
  "site_name": "Example",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

Rejected: negative-count

~~~~
{
  "id": "f:example:post:1",
  "kind": "post",
  "url": "https://openvibe.example/reference",
  "title": "An example post",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "example",
  "site_name": "Example",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {
    "likes": -1
  }
}
~~~~

Rejected: not-a-url

~~~~
{
  "id": "f:example:post:1",
  "kind": "post",
  "url": "openvibe.community/no-scheme",
  "title": "Hello, network",
  "text": "A first post.",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "community",
  "site_name": "Community",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {
    "likes": 4,
    "comments": 2
  }
}
~~~~

Rejected: private-visibility

~~~~
{
  "id": "f:example:post:1",
  "kind": "post",
  "url": "https://openvibe.example/reference",
  "title": "An example post",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "example",
  "site_name": "Example",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {},
  "visibility": "private"
}
~~~~

Rejected: trend-without-direction

~~~~
{
  "id": "f:example:report:1",
  "kind": "report",
  "url": "https://openvibe.example/reference",
  "title": "An example report",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "example",
  "site_name": "Example",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {},
  "trend": {
    "pct": 4
  }
}
~~~~

Rejected: unknown-kind

~~~~
{
  "id": "f:example:post:1",
  "kind": "not-a-kind",
  "url": "https://openvibe.example/reference",
  "title": "An example post",
  "text": "",
  "at": "2026-11-12T09:00:00.000Z",
  "site": "example",
  "site_name": "Example",
  "actor": {
    "name": "Alex",
    "avatar": null
  },
  "subject": "usr_01JQ8Z6K6M0V8Y2Q3R4S5T6V7W",
  "media": null,
  "counts": {}
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('feed.item@1', value);   // { valid, errors: [{ path, message }] }
~~~~
