# Bluesky API: Posting Reference

Bluesky is supported via the AT Protocol. OmniSocials posts to the specific account that completed OAuth. Both `bsky.social` and self-hosted PDS accounts work.

**Channel ID:** `bluesky`

## Supported content types

| Type | Supported |
|------|-----------|
| Feed post | ✅ |
| Story | - |
| Reel | - |

## Minimal example

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "default": "Skeeting from the API 🦋" },
    "accounts": ["your-bluesky-account-id"]
  }'
```

## Platform-specific options

| Field | Type | Description |
|-------|------|-------------|
| `bluesky.thread_parts` | array | Publish as a chained thread instead of a single post. See [Posting threads](#posting-threads). |

## Posting threads

To publish a thread (a reply chain), pass `bluesky.thread_parts`. Each part becomes its own post, chained as a reply to the previous one.

```json
{
  "accounts": ["your-bluesky-account-id"],
  "content": { "default": "" },
  "bluesky": {
    "thread_parts": [
      { "text": "1/3 — kicking off a thread on the AT Protocol 🦋" },
      { "text": "2/3 — every post is a record you own" },
      { "text": "3/3 — portable identity is the whole point" }
    ]
  }
}
```

**Rules:**

- 2 to 25 parts. For a single post, omit `thread_parts` and use `content.bluesky` (or `content.default`) instead.
- Each `text` is required, non-empty, and ≤ 300 graphemes. Parts that exceed the limit are rejected with a `400`.
- `media_urls` is optional per part (max 4 images per part).
- Links, @mentions, and #hashtags are auto-detected and made clickable via AT Protocol rich-text facets — no manual markup needed.

## Media requirements

| Media | Requirement |
|-------|-------------|
| Image | JPEG, PNG, or WebP. Max 1 MB per image. Up to 4 per post. |
| Video | MP4, up to 3 minutes, max 100 MB |

Bluesky has tighter image size limits than most platforms. Pass pre-compressed images to avoid rejections.

**Alt text.** Bluesky renders per-image alt text natively. Pass it by using an object entry instead of a bare string — `{ "url": "https://example.com/bike.jpg", "alt": "A red bicycle leaning against a brick wall" }` in `media_urls` (or `{ "id": "...", "alt": "..." }` in `media_ids`, including per-part thread media; max 1500 characters). It is sent as the image/video embed's `alt`.

## Character limit

Bluesky enforces a 300 **grapheme** limit per post (emoji count as 1 grapheme, not their UTF-8 byte length). OmniSocials counts graphemes correctly when validating.

## Limitations

- Links, mentions, and hashtags are made clickable automatically (via rich-text facets) — you do not need to add any markup
- Bluesky does not expose post scheduling as a first-class concept. Scheduled OmniSocials posts are held on our side and published at the scheduled time.
