---
title: "Mastodon"
description: "Publishing to Mastodon via the Octopost API"
---

# Mastodon

Mastodon supports text posts, images, threads, polls, content warnings, and visibility controls. Because Mastodon is decentralized, each user belongs to a different instance with its own rules and limits. Octopost handles per-instance OAuth registration, character limit detection, media upload API differences, and thread chaining automatically -- you just send us your content.

## Content Limits

| Type | Limit |
|---|---|
| Post text | **Varies by instance** (usually 500 characters, some allow 1,000 to 5,000+) |
| Images per post | **4** |
| Image formats | JPEG, PNG, GIF, WEBP |
| Image file size | Varies by instance (typically 8-16 MB) |
| Poll options | **2 to 4** |
| Video | Varies by instance |

Octopost fetches the actual character limit from each user's instance and validates your content against it before publishing. You do not need to know or track which instance a user is on.

## Good to Know

- **Character limits are not universal.** A 500-character post might work on `mastodon.social` but a user on a different instance might allow 5,000. Octopost detects the limit for each connected account automatically and validates accordingly.
- **Alt text is culturally important.** The Mastodon community strongly values image descriptions. Posts without alt text may receive negative feedback on some instances. Include alt text when you can.
- **Content warnings are a social norm.** Many Mastodon communities expect content warnings for certain topics (spoilers, politics, food, etc.). This is a cultural expectation, not a technical requirement. Use the `spoiler_text` field when appropriate.
- **Federation is not guaranteed.** Posts federate to other instances, but some instances block others. A post may succeed on the user's instance but not reach all followers. Octopost reports success based on the user's instance accepting the post.
- **Polls and media are mutually exclusive.** A post can have a poll or images, but not both. Octopost will reject a request that includes both.

## Content Warnings

Set a content warning to hide the post body behind a "Show more" button with your warning text displayed:

```json
{
  "content": "The villain was actually the hero's father the whole time.",
  "platforms": ["mastodon"],
  "platform_options": {
    "mastodon": {
      "spoiler_text": "Movie spoilers"
    }
  }
}
```

## Visibility Settings

Control who can see your Mastodon post:

| Visibility | Meaning |
|---|---|
| `public` | Visible on public timelines and to everyone (default) |
| `unlisted` | Visible to everyone but not on public timelines |
| `private` | Visible only to followers |
| `direct` | Visible only to mentioned users |

```json
{
  "content": "This is just for my followers.",
  "platforms": ["mastodon"],
  "platform_options": {
    "mastodon": {
      "visibility": "private"
    }
  }
}
```

## Polls

Create polls with 2 to 4 options:

```json
{
  "content": "What's your preferred editor?",
  "platforms": ["mastodon"],
  "poll": {
    "options": ["VS Code", "Neovim", "Zed", "Other"],
    "expires_in": 86400,
    "multiple": false
  }
}
```

## Threads

Send a thread by including an array of content items. Octopost publishes them in order, wiring up the `in_reply_to_id` references so they appear as a connected thread:

```json
{
  "platforms": ["mastodon"],
  "thread": [
    { "content": "A thought that needs more than one post." },
    { "content": "Here is the second part." },
    { "content": "And the conclusion." }
  ]
}
```

## Rate Limits

Rate limits vary by instance -- there is no universal Mastodon rate limit. Octopost tracks rate limit headers from each instance and backs off automatically when limits are approached.

## Error Reporting

| What happened | What Octopost reports |
|---|---|
| Text exceeds instance character limit | Rejected before sending, with the limit for that instance and your character count |
| Instance unreachable | `instance_unavailable` -- includes which instance was unreachable |
| Token expired | Octopost refreshes automatically. If refresh fails: `auth_failed` -- user needs to reconnect their account |
| Poll and media both included | Rejected before sending -- Mastodon does not allow both in a single post |
| Media upload failed | `media_failed` -- with details about which file and why |

## Things Octopost Handles for You

You do not need to worry about any of the following, but in case you are curious:

- **Per-instance OAuth with dynamic app registration.** Because Mastodon is decentralized, there is no single OAuth provider. When a user connects from a new instance, Octopost registers itself as an app on that instance via `POST /api/v1/apps`, then runs the standard OAuth flow against that specific instance. Credentials are stored per-instance.
- **Instance limit detection.** Octopost queries each instance's configuration endpoint to fetch the actual character limit, supported media types, and other constraints. This happens automatically so your content is validated against the correct limits.
- **Token refresh.** Octopost monitors token expiration and refreshes access tokens automatically before they expire.
- **Media upload API fallback.** Some Mastodon instances use the v2 media upload API, others only support v1. Octopost tries v2 first and falls back to v1 transparently.
- **Thread chaining.** Each reply in a thread must reference the previous post's ID via `in_reply_to_id`. Octopost tracks these across the thread and sets them correctly.

## Example

```json
{
  "content": "Hello fediverse! Check out what we have been building. https://octopost.ink",
  "platforms": ["mastodon"],
  "media": {
    "images": [
      { "url": "https://example.com/screenshot.png", "alt": "Dashboard overview" }
    ]
  },
  "platform_options": {
    "mastodon": {
      "visibility": "public"
    }
  }
}
```

That's it. Octopost takes care of the rest.
