TL;DR, Kurze Antwort
9 Min. LesezeitEin Bluesky Feed Generator ist ein Webdienst, der genau eine AT-Protocol-Query implementiert, app.bsky.feed.getFeedSkeleton, und eine Liste von Post-AT-URIs ohne jeden Post-Inhalt zurückgibt. Bluesky hydratisiert diese URIs in der AppView zu echten Posts, bevor der Client sie überhaupt sieht. Der Feed selbst wird über einen app.bsky.feed.generator-Record im eigenen Repo deklariert, der nur did, displayName und createdAt braucht und auf die Service-DID zeigt. Der Dienst weist den Besitz dieser DID über eine .well-known/did.json mit einem Service-Eintrag vom Typ BskyFeedGenerator nach. Anfragen kommen mit einem JWT an, das mit dem Repo-Signaturschlüssel der anfragenden Person signiert ist, und limit ist bei 100 gedeckelt.
Was ist ein Bluesky Feed Generator?
Jeder benutzerdefinierte Feed in der Bluesky-App wird von einem Bluesky Feed Generator bedient, einem schlichten HTTPS-Dienst, der genau eine Query beantwortet und nichts außer einer Liste von Post-Adressen zurückgibt. Er hält keinen Post-Text, keine Avatare und keine Like-Zahlen. Er sortiert, und das war es.
Das offizielle Starter Kit sagt es deutlich: „the server receives a request from a user's server and returns a list of post URIs with some optional metadata attached. Those posts are then hydrated into full views by the requesting server and sent back to the client.“
Diese Aufteilung ist das ganze Design. Dein Dienst entscheidet, welche Posts und in welcher Reihenfolge. Die Infrastruktur von Bluesky macht aus den Adressen darstellbare Posts.
Welches Lexicon implementiert ein Feed Generator?
Genau eines: app.bsky.feed.getFeedSkeleton. Das Lexicon beschreibt es als „Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service.“
Vier Lexicons umgeben den Ablauf, und zu wissen, welche Seite welches implementiert, spart einen Tag Debugging:
| Lexicon | Typ | Implementiert von | Zweck |
|---|---|---|---|
app.bsky.feed.generator | record | dem Repo der erstellenden Person | Erklärt, dass der Feed existiert, und nennt die Service-DID |
app.bsky.feed.getFeedSkeleton | query | deinem Feed Generator | Liefert die sortierte Liste der Post-URIs |
app.bsky.feed.describeFeedGenerator | query | deinem Feed Generator | Listet auf, welche Feed-URIs dieser Dienst bedient |
app.bsky.feed.getFeed | query | der Bluesky AppView | Liefert den hydratisierten Feed, den der Client tatsächlich rendert |
describeFeedGenerator ist ausdrücklich keine AppView-Methode: „Does not require auth; implemented by Feed Generator services (not App View).“ Rufst du es auf der AppView auf, kommt {"error":"MethodNotImplemented","message":"Method Not Implemented"} zurück, während derselbe Aufruf auf dem Discover-Dienst von Bluesky unter discover.bsky.app jede Feed-URI liefert, die dieser Dienst hostet.
Was steht im app.bsky.feed.generator-Record?
Der Record ist das, was den Feed in der App erscheinen lässt, und er liegt in deinem eigenen Repo statt auf deinem Server. Das Lexicon nennt ihn einen „Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.“
| Feld | Typ | Pflicht | Constraint |
|---|---|---|---|
did | string, format: did | ja | Die DID des Dienstes, der den Feed ausliefert |
displayName | string | ja | maxGraphemes: 24, maxLength: 240 |
createdAt | string, format: datetime | ja | |
description | string | nein | maxGraphemes: 300, maxLength: 3000 |
descriptionFacets | array | nein | app.bsky.richtext.facet-Einträge |
avatar | blob | nein | accept: image/png, image/jpeg, maxSize: 1000000 |
acceptsInteractions | boolean | nein | Meldet den Feed für app.bsky.feed.sendInteractions an |
contentMode | string | nein | contentModeUnspecified oder contentModeVideo |
Drei Felder sind Pflicht, und keines davon ist der Algorithmus. Beachte die Grenze von 24 Graphemen für displayName, kurz genug, dass die meisten Feed-Namen gekürzt werden, bevor du es merkst, und die Avatar-Obergrenze von 1.000.000 Bytes. Der Unterschied zwischen Graphem und Byte hinter diesen Zahlen ist derselbe, über den Leute bei Bluesky Facet Byte-Offsets stolpern.
contentMode ist das einzige Feld, das das Rendering ändert. app.bsky.feed.defs#contentModeVideo ist ein Token, das „Declares the feed generator returns posts containing app.bsky.embed.video embeds,“ und den Client in den Vollbild-Videoplayer schaltet.
Der rkey des Records wird zum öffentlichen Bezeichner des Feeds. Der Discover-Feed von Bluesky selbst wird als at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot adressiert, wobei die DID zum Konto bsky.app gehört und whats-hot der Record Key ist.
Wie findet Bluesky meinen Dienst?
Über ein DID-Dokument mit einem Service-Eintrag vom Typ BskyFeedGenerator. Der Record gibt Bluesky eine DID. Das Auflösen dieser DID gibt Bluesky eine URL.
Das Starter Kit richtet das als did:web ein, deine eigene Domain liefert das Dokument also unter /.well-known/did.json aus. Der produktive Discover-Dienst von Bluesky macht dasselbe, und du kannst ihn abrufen:
{
"id": "did:web:discover.bsky.app",
"service": [
{
"id": "#bsky_fg",
"type": "BskyFeedGenerator",
"serviceEndpoint": "https://discover.bsky.app"
}
]
}Die id #bsky_fg und der type BskyFeedGenerator sind beide feste Strings. Ist einer von beiden falsch, löst der Feed ins Leere auf.
Zwei Hosting-Vorgaben stammen aus der README und nicht aus einer Spezifikation. „Your feed will need to be accessible at the value supplied to the FEEDGEN_HOSTNAME environment variable,“ und „The service must be set up to respond to HTTPS queries over port 443.“ Reines HTTP ist keine Option, ein nicht standardmäßiger Port ebenso wenig.
Das Starter Kit weist außerdem auf eine Migrationsfalle hin, die du vor der Wahl einer Domain beachten solltest: Du willst womöglich did:plc statt did:web, „if you expect this Feed Generator to be long-standing and possibly migrating domains.“ Eine did:web ist dein Hostname. Wechselst du den Host, bricht jeder veröffentlichte Record, der darauf zeigt. Eine did:plc übersteht den Umzug.
AdaptlyPost
7-Tage-Testversion starten
Plattformübergreifende Analysen
Sozialer Posteingang
KI-gestützter Assistent

Was liefert getFeedSkeleton zurück?
Einen Cursor und ein Array aus app.bsky.feed.defs#skeletonFeedPost-Einträgen, von denen jeder eine AT-URI plus optionalen Kontext enthält. Hier eine echte Antwort des Discover-Dienstes von Bluesky, auf zwei Einträge gekürzt:
{
"cursor": "eyJvIjoiMjAyNi0wOS0xMVQxMDo0OTowNi42NTI4NDk4NzVaIiwibiI6IjIwMjYtMDktMTFUMjI6NDk6MDYuNjUyODQ5ODc1WiIsInNzIjoiMjAyNi0wOS0xMVQyMjo0OTowNi42NTI4NDk4NzVaIiwibHUiOiIyMDI2LTA5LTExVDIyOjQ5OjA2LjY1NjYyMzMwM1oiLCJwZyI6MSwibSI6IkFRQUFBQUFBQUFBSkFBQUFPakFBQUFJQUFBRHNWZ0FBa1ZjQUFCZ0FBQUFhQUFBQUtMTTNrUT09In0=",
"feed": [
{
"feedContext": "th-nature_animals_pets",
"post": "at://did:plc:mpvivcve3y2fdlw7pmq7niwz/app.bsky.feed.post/3mvb4r2qh4k2r"
},
{
"feedContext": "th-science_research",
"post": "at://did:plc:fb57h2fswm7s2qqzxflizlk7/app.bsky.feed.post/3mvasir35ec2e"
}
]
}Pflicht ist in jedem Eintrag nur post. feedContext ist optional und auf maxLength: 2000 begrenzt, im Lexicon beschrieben als „Context that will be passed through to client and may be passed to feed generator back alongside interactions.“ Bluesky nutzt es für Themen-Labels, wie der String th-nature_animals_pets oben zeigt. Die Antwort kann außerdem reqId tragen, einen „Unique identifier per request,“ begrenzt auf 100 Zeichen.
Der Parameter limit hat einen dokumentierten Boden und eine dokumentierte Decke: minimum: 1, maximum: 100, default: 50, und die AppView setzt das durch. Eine Anfrage über 101 liefert:
{ "error": "InvalidRequest", "message": "Invalid app.bsky.feed.getFeed params: integer too big (maximum 100, got 101)" }Die Paginierung läuft über einen undurchsichtigen Cursor, den dein Dienst selbst erfindet. Das Starter Kit ist bei der gewünschten Form deutlich: „We strongly encourage that the cursor be unique per feed item to prevent unexpected behavior in pagination. We recommend, for instance, a compound cursor with a timestamp + a CID.“
Wie wird die Anfrage authentifiziert?
Mit einem JWT, das mit dem Repo-Signaturschlüssel der anfragenden Person signiert ist, und das Starter Kit schreibt Header wie Payload aus:
const header = {
type: 'JWT',
alg: 'ES256K', // (key algorithm) - in this case secp256k1
};
const payload = {
iss: 'did:example:alice', // (issuer) the requesting user's DID
aud: 'did:example:feedGenerator', // (audience) the DID of the Feed Generator
exp: 1683643619, // (expiration) unix timestamp in seconds
};Die Prüfung ist optional, und die Empfehlung hängt davon ab, was dein Feed tut: „If you are creating a generic feed that does not differ for different users, you do not need to check auth. But if a user's state (such as follows or likes) is taken into account, we strongly encourage you to validate their auth token.“ Ein Feed, der nach Follows personalisiert, ohne iss zu prüfen, ist ein Feed, den jeder als jeder beliebige andere abfragen kann.
Der Discover-Dienst von Bluesky beantwortet nicht authentifizierte getFeedSkeleton-Anfragen mit einem echten Skeleton, ein günstiger Weg, das eigene Verständnis des Endpunkts zu prüfen.

- Keine Follows, keine Likes, kein nutzerspezifischer Zustand
- Auth-Prüfung ist optional
- Ranking liest die iss-DID
- Ohne Prüfung kann jeder als jeder abfragen
Woher kommen die Posts?
Aus der Firehose, in fast jeder realen Implementierung: „For most use cases, we recommend subscribing to the firehose at com.atproto.sync.subscribeRepos. This websocket will send you every record that is published on the network.“
Weil ein Feed Generator nie Inhalte ausliefert, kann er fast alles davon wegwerfen. „Unless your algorithm is intended to provide posts you missed or something similar, you can likely garbage collect any data that is older than 48 hours.“
Die drei Formen, die das Starter Kit skizziert, decken die meisten Feeds ab, die Leute bauen. Ein What's-hot-Feed zählt Likes pro Post und liefert aktuelle Posts oberhalb einer Schwelle. Ein Community-Feed stellt eine Liste von DIDs zusammen und liefert alles, was diese posten. Ein thematischer Feed schickt Post-Text durch einen Keyword-Matcher oder ein Modell. Die Firehose steht neben den übrigen Bluesky API Rate Limits, in denen du dich bewegst.
Was lassen die Docs offen?
Einiges, und die Lücken sind die Stellen, an denen Feed Generators in Produktion brechen statt im Test.
Der deklarierte Fehler passt nicht zum ausgelieferten Fehler. Sowohl getFeedSkeleton als auch getFeed deklarieren in ihren Lexicons genau einen Fehler, UnknownFeed. Fragst du die AppView nach einem Feed, den es nicht gibt, bekommst du {"error":"InvalidRequest","message":"could not find feed"}. Fragst du den Discover-Dienst von Bluesky selbst, bekommst du {"message":"no such feed: \"nope-not-real\""}, ganz ohne error-Feld. Zwei Formen in Produktion, keine davon der dokumentierte Name.
Das Starter Kit ist älter als das Lexicon, das es beschreibt. Sein Abschnitt zu den Skeleton-Metadaten sagt „for now, the only defined reason is a repost, but this is open to extension.“ Das aktuelle skeletonFeedPost hat eine reason-Union aus zwei Mitgliedern, #skeletonReasonRepost und #skeletonReasonPin. Baust du gegen den Reason-Typ der README, erfährst du nie, dass es Pinning gibt.
AdaptlyPost
7-Tage-Testversion starten
Plattformübergreifende Analysen
Sozialer Posteingang
KI-gestützter Assistent
Auch die Hydration-Geschichte hat sich verschoben. Die README sagt „In the future, the PDS will hydrate the feed with the help of an App View, but for now, the PDS handles hydration itself,“ während das getFeed-Lexicon inzwischen schlicht „Implemented by App View.“ sagt.
Für das Skeleton selbst ist kein Limit veröffentlicht. limit deckelt eine Anfrage bei 100 Einträgen, aber das feed-Array der Antwort trägt keine maxLength, und nirgends steht, was passiert, wenn ein Generator mehr zurückgibt als angefragt. getFeedGenerator liefert isOnline und isValid, beschrieben als ob der Dienst „has been online recently, or else seems to be inactive“ und ob er „is compatible with the record declaration.“ Weder „recently“ noch „compatible“ ist irgendwo definiert.
Wie veröffentliche ich den Feed?
Indem du den Record mit com.atproto.repo.putRecord in dein eigenes Repo schreibst. Das Publish-Skript des Starter Kits macht genau das, mit collection auf app.bsky.feed.generator, dem rkey auf den Kurznamen, der zur URL des Feeds wird, und did auf did:web:$FEEDGEN_HOSTNAME, sofern du keine eigene Service-DID angegeben hast. Name, Beschreibung oder Avatar zu ändern ist derselbe Aufruf mit demselben rkey. Es gibt keine Freigabewarteschlange.
All das ersetzt kein normales Posten. Ein Feed Generator entscheidet, was Lesende sehen; die eigenen Posts ins Netzwerk zu bekommen bleibt Bluesky-Posts planen über einen ganz normalen Record-Write, was AdaptlyPost zusammen mit den anderen Netzwerken erledigt, auf denen es veröffentlicht, während die Interaktionen danach in den Bluesky Analytics landen.
Häufig gestellte Fragen
Brauche ich einen Server, um einen Bluesky Feed Generator zu betreiben?
Ja. Der Feed-Record liegt in deinem Repo, aber getFeedSkeleton muss ein Dienst beantworten, den du selbst hostest, über HTTPS auf Port 443 unter dem Hostnamen, auf den dein DID-Dokument zeigt. Es gibt keinen Weg, einen eigenen Feed rein innerhalb von Bluesky zu definieren.
Kann ein Feed Generator Post-Inhalte sehen?
Nur, was er selbst sammelt, meist aus der Firehose. Das Skeleton, das er zurückgibt, trägt AT-URIs und sonst nichts, und die Hydration passiert auf Bluesky-Seite, nachdem dein Dienst geantwortet hat.
Was ist der Unterschied zwischen getFeed und getFeedSkeleton?
getFeedSkeleton gehört dir und liefert skeletonFeedPost-Einträge, also nackte URIs. getFeed gehört der AppView und liefert feedViewPost-Einträge, also vollständige Posts mit Autorenprofilen und Zählern. Clients rufen getFeed auf. Dein Dienst nie.
Woran erkenne ich, dass mein Feed kaputt ist?
Ruf app.bsky.feed.getFeedGenerator mit der AT-URI deines Feeds auf. Es liefert isOnline und isValid neben der Feed-Ansicht. Bluesky veröffentlicht nicht, wie kürzlich ein Dienst geantwortet haben muss, um als online zu gelten, behandle ein false also als Anlass, deine eigenen Logs zu prüfen.
Kann ich für einen Feed Geld verlangen oder einschränken, wer ihn sieht?
Das Protokoll gibt dir den Haken und keine Abrechnung. Auth ist bei getFeedSkeleton optional, und wenn sie vorliegt, trägt das JWT die DID der anfragenden Person im iss-Claim, eine Beschränkung auf eine Liste von DIDs ist also unkompliziert. Bezahlung, und wie einer abgewiesenen Person das mitgeteilt wird, bleiben vollständig deinem Dienst überlassen.
Wie messe ich, wie ein Feed läuft?
Bluesky veröffentlicht eine einzige Zahl über einen Feed, den likeCount auf seiner generatorView. Alles andere zur Leistung des Feeds musst du selbst protokollieren, aus den Anfragen, die auf deinem eigenen Dienst eingehen. Dieser Mangel ist derselbe, der unter hat Bluesky Analytics behandelt wird.
Was passiert, wenn ein Client mehr als 100 Einträge von einem Feed anfordert?
Die AppView setzt diese Obergrenze selbst durch. Eine Anfrage an getFeed mit limit 101 liefert einen InvalidRequest-Fehler, der besagt, dass die Ganzzahl zu groß ist, mit einem Maximum von 100. Der limit-Parameter hat ein Minimum von 1, ein Maximum von 100 und einen Default von 50, sodass ein Feed Generator diese Ablehnung nie selbst bauen muss.
Warum empfiehlt das Starter Kit did:plc statt did:web für manche Feed Generatoren?
Ein did:web ist einfach dein Hostname, ein Domainwechsel bricht also jeden veröffentlichten Record, der darauf zeigt. Ein did:plc übersteht einen Umzug, deshalb rät das Starter Kit dazu bei allem, was langlebig sein oder migrieren könnte.
Wie sollte der Pagination-Cursor eines Feed Generators aufgebaut sein?
Das Lexicon deklariert cursor als einfachen String ohne Einschränkungen, und das Starter Kit nennt den Wert opak und vollständig die Entscheidung des Generators. Das Starter Kit empfiehlt einen zusammengesetzten Cursor aus Zeitstempel und CID und betont, dass er pro Feed-Eintrag eindeutig sein sollte, um Pagination-Fehler zu vermeiden.
Was bewirkt acceptsInteractions im app.bsky.feed.generator-Record?
Setzt man acceptsInteractions auf true, meldet sich der Feed für app.bsky.feed.sendInteractions an, wodurch Clients zurückmelden können, mit welchen Posts ein Nutzer interagiert hat. Das Feld ist optional und boolesch, kein Teil der Docs verlangt es für einen funktionierenden Feed.
Setze das mit AdaptlyPost in die Praxis um
War dieser Artikel hilfreich?
Teilen Sie uns Ihre Meinung mit!
Sieh uns öfter bei Google
Ein Klick macht AdaptlyPost zu einer bevorzugten Quelle. Unsere Artikel stehen dann weiter oben in deinen Top-Meldungen, im KI-Modus und in den KI-Übersichten.
Bevor Sie gehen...
AdaptlyPost
Planen Sie Ihre Inhalte für alle Plattformen
Verwalten Sie alle Ihre Social-Media-Konten an einem Ort mit AdaptlyPost.
Plattformübergreifende Analysen
Sozialer Posteingang
KI-gestützter Assistent
Verwandte Glossarbegriffe


Warum Bluesky facets byteStart byteEnd Bytes zählen, keine Zeichen
Warum Bluesky facets byteStart byteEnd UTF-8-Bytes zählen, nicht die UTF-16-Indizes von JavaScript. Der Hinweis im Lexicon, ein Rechenbeispiel und Code dazu.


Warum das Bluesky-Bildgrößenlimit bei 2,000,000 Bytes liegt
Das Bluesky-Bildgrößenlimit liegt bei 2,000,000 Bytes pro Beitragsbild, gesetzt per maxSize im images-Lexicon. Avatare und Banner enden bei 1,000,000 Bytes.


So rechnest du das Bluesky-API-Rate-Limit in Posts pro Stunde um
Das Bluesky-API-Rate-Limit für Schreibvorgänge ist ein Punktebudget, kein Anfragezähler: 5,000 Punkte pro Stunde, 3 pro Post, also 1,666 Posts pro Stunde.
Verwandte Artikel


Die zwei Records hinter einem Bluesky-Handle mit eigener Domain
Ein Bluesky-Handle mit eigener Domain braucht einen von zwei Records: TXT auf _atproto oder Klartext unter /.well-known/atproto-did. Plus die gesperrten TLDs.


Meta setzt das alt_text-Zeichenlimit der Instagram API auf 1.000
Meta begrenzt das alt_text-Zeichenlimit der Instagram API auf 1.000 Zeichen und beschränkt es auf Standbilder. Reels und Stories nehmen keinen Alt-Text an.


Hinter der TikTok KI-Kennzeichnung stecken zwei Labels
Die TikTok KI-Kennzeichnung gibt es zweimal: ein Creator-Label, das du per is_aigc setzt, und ein Auto-Label durch KI-Effekte oder C2PA, das bleibt.

