# Publish to Bluesky

> Publish a Bluesky post or thread, with up to four images or one video.

Canonical: <https://www.tryadeli.com/docs/api/posts/publish-bluesky>

`POST https://app.tryadeli.com/api/v1/posts`

Authorization: `Bearer <API key>` — see [Authentication](https://www.tryadeli.com/docs/authentication)

A Bluesky post is text with up to four images or one video. Send `thread` to publish several posts,
each a reply to the one before. Bluesky publishes synchronously, so the response
is the finished post. Bluesky's API is free: nothing is billed per post.

Links, `@mentions`, and `#hashtags` in `text` become links on Bluesky. A mention
of a handle that doesn't exist stays plain text.

**Body**

| Name        | Type        | Required | Description                                                                                                                                                                                                                                                                                                                                                                 |
| ----------- | ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platform`  | `"bluesky"` | Yes      |                                                                                                                                                                                                                                                                                                                                                                             |
| `accountId` | `uuid`      | Yes      | A connected Bluesky account in your organization.                                                                                                                                                                                                                                                                                                                           |
| `profileId` | `uuid`      |          | Optional, because accountId names its profile. If you pass both, they must agree.                                                                                                                                                                                                                                                                                           |
| `text`      | `string`    | Yes      | Up to 300 characters, counted as a reader sees them (an emoji is one), and 3,000 bytes. Links count at full length. Send `""` for an images-only post.                                                                                                                                                                                                                      |
| `images`    | `object[]`  |          | Up to 4, each `{ content_type, base64, altText }` with `image/jpeg`, `image/png`, or `image/webp`. Images over 2 MB are resized to fit, and the response lists them in `resizedImages`. `altText` is optional but recommended.                                                                                                                                              |
| `video`     | `object`    |          | One MP4, never with `images`: `{ content_type: "video/mp4", base64, altText }` or `{ url, altText }` for a public HTTPS URL, up to 100 MB. Bluesky processes it before the post is created, which can take a minute. Bluesky limits videos per account per day and needs a verified email first; past that, this returns `422 upload_limit_exceeded` with Bluesky's reason. |
| `langs`     | `string[]`  |          | Up to 3 language tags, such as `en` or `pt-BR`, which Bluesky uses for feeds and translation.                                                                                                                                                                                                                                                                               |
| `thread`    | `object[]`  |          | Up to 24 further posts, each `{ text, images, video }` with the same rules, published in order as replies.                                                                                                                                                                                                                                                                  |

`201` when every post is live. `providerId` is the post's AT URI.

A thread that fails part-way returns `207` with `status: "partial"`, exactly as
[for X](https://www.tryadeli.com/docs/api/posts/publish-x).

## Request examples

**cURL**

```bash
curl --fail-with-body -X POST "https://app.tryadeli.com/api/v1/posts" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "platform": "bluesky",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "text": "Herons fish at dawn. A thread:",
  "thread": [
    {
      "text": "They stand still for minutes at a time."
    }
  ]
}'
```

**JavaScript**

```javascript
const response = await fetch("https://app.tryadeli.com/api/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ADELI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    platform: "bluesky",
    accountId: "00000000-0000-0000-0000-000000000000",
    text: "Herons fish at dawn. A thread:",
    thread: [
      {
        text: "They stand still for minutes at a time.",
      },
    ],
  }),
});
console.log(await response.json());
```

**Python**

```python
import os

import requests

response = requests.post(
    "https://app.tryadeli.com/api/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['ADELI_API_KEY']}"},
    json={
        "platform": "bluesky",
        "accountId": "00000000-0000-0000-0000-000000000000",
        "text": "Herons fish at dawn. A thread:",
        "thread": [
            {
                "text": "They stand still for minutes at a time.",
            },
        ],
    },
)
print(response.json())
```

## Responses

### 201

```json
{
  "id": "bluesky_post_00000000-0000-0000-0000-000000000000_3l6oveex3ii2l",
  "platform": "bluesky",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "status": "published",
  "providerId": "at://did:plc:abc123/app.bsky.feed.post/3l6oveex3ii2l",
  "permalink": "https://bsky.app/profile/adeli.bsky.social/post/3l6oveex3ii2l",
  "posts": [
    { "index": 0, "status": "published", "providerId": "at://did:plc:abc123/app.bsky.feed.post/3l6oveex3ii2l", "url": "https://bsky.app/profile/adeli.bsky.social/post/3l6oveex3ii2l" },
    { "index": 1, "status": "published", "providerId": "at://did:plc:abc123/app.bsky.feed.post/3l6ovefa2kq2k", "url": "https://bsky.app/profile/adeli.bsky.social/post/3l6ovefa2kq2k" }
  ]
}
```

### 422

```json
{
  "error": {
    "code": "invalid_media",
    "message": "The image could not be made small enough for Bluesky"
  }
}
```
