FeedItem feed.item@1

Generated at from openvibe-contracts v0.419.0 and openvibe-sdk v0.161.0.

Version
1.0.0
Owner
network
Visibility
public
Status
active
Compatibility
backward
Schema
https://openvibe.network/contracts/feed/item.v1.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

FieldTypeRequiredDescriptionConstraints
idstringyesStable across feeds and restarts: f:<service>:<type>:<id> for a source item and t:<site>:<url> for a trending card.
  • minLength 1
  • maxLength 500
kindenumyesWhat 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"
urlstringyesThe thing itself: the post, stream, job board, report page or wiki page.
  • pattern ^https?://
  • maxLength 2000
titlestringyes
  • maxLength 300
textstringyesThe source's first 280 characters, on one line; empty when it has none.
  • maxLength 280
atstring | nullyesWhen it happened; null only for a trending card.
  • format date-time
starts_atstringWhen a scheduled event begins; present only on event cards.
  • format date-time
sitestringyesThe source service: live, community, media-hub, pics, games, quest, chat, today, work, deals, news, report, wiki, blog, tools, network…
  • maxLength 40
site_namestringyes
  • maxLength 40
actorobject | nullyesWho did it; null for AI, system, aggregate and trending cards.
subjectstring | nullyesThe 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}$
mediaobject | nullyesThe card's first picture, as a Media thumbnail; null when it has none.
countsobjectyesThe numbers the source knows; each present only when it knows it.
  • no other fields
counts.viewsinteger
  • minimum 0
counts.likesinteger
  • minimum 0
counts.commentsinteger
  • minimum 0
counts.watchersintegerPeople who watched a stream (peak for a past broadcast).
  • minimum 0
counts.peopleintegerA count a report card is about (people online, jobs, stories).
  • minimum 0
live_nowbooleanPresent on live and stream cards: true only while the channel is on air.
trendobjectPresent on report cards: which way the number moved and by how much.
  • no other fields
trend.directionenumyes
  • one of "up", "down", "flat"
trend.pctnumber
  • minimum 0
trend.previousinteger
  • minimum 0
groupobjectA run of the same kind folded into one card (two or more items).
  • no other fields
group.countintegeryes
  • minimum 2
group.itemsarray of objectyes
  • minItems 2
  • items: no other fields
group.items[].idstringyes
group.items[].urlstringyes
  • pattern ^https://
group.items[].titlestringyes
group.items[].mediaobject | nullyes
labelenumPresent only when the card needs one.
  • one of "Trending", "AI", "System", "Live"
visibilityenumPresent 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 }] }