# Mastodon API: Posting Reference

Mastodon is supported via the standard Mastodon API. OmniSocials posts to the specific instance and account that completed OAuth. Because Mastodon is decentralized, every account lives on a different server and is identified by its full `user@instance.tld` handle.

**Channel ID:** `mastodon`

## Supported content types

| Type | Supported |
|------|-----------|
| Feed post (toot) | ✅ |
| 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": "Posting from the API 🐘" },
    "accounts": ["your-mastodon-account-id"]
  }'
```

## Platform-specific options

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

Content warnings and per-post visibility are not yet exposed and may be added as optional keys in the future.

## Posting threads

To publish a thread, pass `mastodon.thread_parts`. Each part becomes its own toot, replying to the previous one natively.

```json
{
  "accounts": ["your-mastodon-account-id"],
  "content": { "default": "" },
  "mastodon": {
    "thread_parts": [
      { "text": "1/3: a thread on the fediverse 🐘" },
      { "text": "2/3: every instance sets its own rules" },
      { "text": "3/3: and that is a feature, not a bug" }
    ]
  }
}
```

**Rules:**

- 2 to 25 parts. For a single toot, omit `thread_parts` and use `content.mastodon` (or `content.default`) instead.
- Each `text` is required, non-empty, and within your instance's character limit (500 on mastodon.social; OmniSocials reads your instance's real limit at connect time).
- `media_urls` is optional per part (max 4 items per part).

## Media requirements

Mastodon media limits vary by instance. OmniSocials validates against the default mastodon.social limits:

| Media | Requirement (default) |
|-------|-----------------------|
| Image | JPEG, PNG, GIF, or WebP. Max 16 MB. Up to 4 per post. |
| Video | MP4, max 99 MB. Duration is instance-dependent; OmniSocials does not enforce a fixed cap. |

Mastodon does not support mixing images and video in the same status: attach images only, or a single video. Mixed media returns a `400 validation_error` at create/schedule time.

Some instances are more permissive. Some are stricter. A post that passes OmniSocials validation may still be rejected by a custom instance with tighter limits. In that case the error body includes the instance's rejection message.

### Alt text

The Mastodon community strongly values alt text on images; several instances even run bots reminding users to add descriptions. Pass it per media item by using an object entry instead of a bare string (works in `media_urls`, `media_ids`, and per-part thread media; max 1500 characters). It is sent as the media's `description`, and the same `alt` also delivers to Bluesky, X, Pinterest, Instagram (images), and LinkedIn (images):

```json
{
  "content": { "default": "Morning ride before work 🐘" },
  "accounts": ["your-mastodon-account-id"],
  "media_urls": [
    { "url": "https://example.com/bike.jpg", "alt": "A red bicycle leaning against a brick wall" }
  ]
}
```

## Character limit

Your instance's real limit applies. When you connect a Mastodon account, OmniSocials reads the instance's configured `max_characters` and validates against that, so an instance with a 5,000-character limit really gets 5,000. The fallback is 500 (the mastodon.social default) when the instance does not report a limit.

## Limitations

- Content warnings (CW) are not currently exposed through the API
- Per-post visibility (`public`, `unlisted`, `private`, `direct`) defaults to the account's default. A field to override this is planned but not shipped.
- Federation delays may mean the post is not immediately visible on other instances even after a successful publish response
