Posts & Threads
Publish a text, image, video or thread post to Bluesky, set the post language, and every platformSpecificData field.
A plain post becomes a text post. Media in mediaItems makes an image or video post, and threadItems in platformSpecificData on the Bluesky entry makes a thread.
Text post
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: published } = await zernio.posts.createPost({
body: {
content: 'Shipped a new feature today. Notes at https://example.com/changelog',
platforms: [
{ platform: 'bluesky', accountId: '66b2e19d8c3f5a7e9d0b1c2d' }
],
publishNow: true
}
});
console.log(published.post.platforms[0].platformPostUrl);Response (201):
{
"post": {
"_id": "65f1c0a9e2b5af0012ab34cd",
"status": "published",
"platforms": [
{
"platform": "bluesky",
"status": "published",
"platformPostUrl": "https://bsky.app/profile/yourhandle.bsky.social/post/..."
}
]
}
}Every sample below changes only the mediaItems or the platforms entry of this request.
Zernio converts the text to AT Protocol facets on its own:
@handle.bsky.socialbecomes a profile link.#hashtagbecomes a clickable hashtag.- A URL becomes a clickable link with a preview card.
Bluesky builds the link card from the target page's Open Graph tags. Put the URL at the end of the post, and check that the page has them.
Image post
Up to 4 images. Alt text goes in altText on the media item:
"mediaItems": [
{ "type": "image", "url": "https://cdn.example.com/photo1.jpg", "altText": "The launch screen" },
{ "type": "image", "url": "https://cdn.example.com/photo2.jpg" },
{ "type": "image", "url": "https://cdn.example.com/photo3.jpg" },
{ "type": "image", "url": "https://cdn.example.com/photo4.jpg" }
]Video post
One MP4 per post, at most 50 MB and 60 seconds:
"mediaItems": [
{ "type": "video", "url": "https://cdn.example.com/demo.mp4" }
]Thread
threadItems publishes a chain of posts. The first item is the root post, and every later item replies to the one before it.
Each item has its own text and media, and its own 300-character limit.
When threadItems is set, the top-level content is used for display and search only and is not published. The first post goes in threadItems[0]:
{
"platform": "bluesky",
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"platformSpecificData": {
"threadItems": [
{
"content": "A thread about building APIs",
"mediaItems": [{ "type": "image", "url": "https://cdn.example.com/api.jpg" }]
},
{ "content": "First, design your endpoints around resources, not actions." },
{ "content": "Second, version your API from day one." },
{ "content": "Third, document everything. Your future self will thank you." }
]
}
}Post language
langs tags the post with 1 to 3 BCP-47 codes. Bluesky feed generators filter on this field, so a post without it never appears in language-scoped feeds.
It is set at creation only, because Bluesky has no post editing. Every item of a thread carries the same langs:
{
"platform": "bluesky",
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"platformSpecificData": { "langs": ["pt", "en"] }
}When a post has no langs, Zernio applies the account's default at publish time.
A per-post langs always wins. The default applies to posts published after the change.
Read the account default with Get Bluesky account settings and set it with Update Bluesky account settings:
await zernio.accounts.updateBlueskySettings({
path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d' },
body: { defaultLangs: ['pt'] }
});Response (200), the updated settings. GET /v1/accounts/{accountId}/bluesky-settings reads the same shape back:
{
"defaultLangs": ["pt"]
}Send "defaultLangs": null to clear the default.
Platform fields
All fields go in platformSpecificData on the Bluesky entry.
| Field | Type | Default | Description |
|---|---|---|---|
langs | Array<string> | account default | 1 to 3 BCP-47 codes (pt, en-US) written to the post's langs field. Absent when neither the post nor the account sets one. |
threadItems | Array<{content, mediaItems?}> | The whole thread, root post first. Top-level content is then display and search only. |