# Mastodon

Source: https://postgale.com/docs/mastodon

Publish statuses, threads and replies with media, alt text, content warnings and visibility to any Mastodon server through the Postgale API and MCP server.

Postgale publishes to Mastodon accounts on any server: statuses with media and alt text, content warnings, visibility, threads and
replies, and deletes of what was published. Everything is available over the [REST API](https://postgale.com/docs/api) (paths relative to `https://postgale.com/api/v1`) and
the [MCP server](https://postgale.com/docs/mcp) (`connect_account`, `create_post`, `manage_published_post`). Each connected account has platform `mastodon`;
its `platformAccountId` is `<account id>@<server>`.

## Connect an account

Mastodon runs on many independent servers, so connecting starts with yours.

1. In Postgale open **Dashboard → Accounts → Mastodon** and enter your server's domain: for `@you@hachyderm.io` that is `hachyderm.io`.
2. You are sent to your server's own sign-in and authorization page. Approve Postgale. It asks for Mastodon's standard `read`, `write`,
   `follow` and `push` scopes (no admin scopes): `read` covers your account data, `write` posting and editing, `follow` your follows,
   blocks and mutes, and `push` push notification subscriptions. Postgale uses them only to read your profile and your server's limits
   and to upload media, post and delete statuses; it does not follow, block or mute anyone or subscribe to notifications.
3. Back in Postgale the account is listed as `@you@your.server`.

Or ask for a link from your code (`instance` is required):

GET /api/v1/connect/mastodon?instance=hachyderm.io:
```bash
curl https://postgale.com/api/v1/connect/mastodon?instance=hachyderm.io \
  -H 'Authorization: Bearer sk_…'

# {"authUrl":"https://postgale.com/api/oauth/mastodon/consent?state=…","expiresInSeconds":600}
```

The first time anyone connects an account on a server, Postgale registers itself there as an app; later connections reuse it. Only
public domain names are accepted (no IP addresses, ports or local names). A server that is not Mastodon-compatible answers 400.
Mastodon access tokens do not expire. If you revoke Postgale under **Preferences → Account → Authorized apps**, the next call fails:
the account becomes `needs_reconnect` and `account.disconnected` is sent (reason `token_revoked`). Connecting again updates the same
account. Disconnecting the account in Postgale revokes the token on your server.

## Publish

The post content is the status text. The character limit is your server's (500 on most servers, more on some); as on Mastodon, every
link counts 23 characters, `@user@other.server` counts as `@user`, and a content warning counts toward the limit. Media is uploaded by
Postgale, with the alt text you give, and the status is posted once your server has processed it.

POST /api/v1/posts:
```bash
curl -X POST https://postgale.com/api/v1/posts \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -d '{"content":"Our spring release is out: https://example.com/blog/spring #release","platforms":[{"accountId":"acc_masto","platformSpecificData":{"mediaUrls":["https://media.postgale.com/…/hero.jpg"],"altTexts":["The new dashboard in dark mode"],"visibility":"public","language":"en"}}],"publishNow":true}'
```

| platformSpecificData | What it does |
| --- | --- |
| `mediaUrl` / `mediaUrls` | Attachments: up to your server's maximum (usually 4 images), or one video or audio file. Public https URLs, e.g. an uploaded media `publicUrl`. Size limits are the server's |
| `altText` / `altTexts` | Alt text (description) per attachment, ≤1500 characters |
| `visibility` | `public` (default), `unlisted`, `private` (followers only) or `direct` (mentioned people only) |
| `spoilerText` | Content warning shown before the text; media is then marked sensitive |
| `sensitive` | Mark the media sensitive without a content warning |
| `language` | ISO 639 code of the text, e.g. `en` |
| `threadItems` | A thread: up to 25 statuses of `{content, mediaUrl?, mediaUrls?, altText?, altTexts?}`, each a reply to the previous one, with the same visibility. Replaces the post text |
| `inReplyToId` | Reply to an existing status on your server (its id) |

Limits are checked when the post is created (with the limits your server reported when you connected) and again when publishing. The
target's `platformPostId` is the status id and `url` the status link on your server. For a thread they are those of the first status; if
a later status fails, the thread stops there and the target gets a warning. Each status is sent with an `Idempotency-Key`, so a retried
request never posts twice.

## Delete

| Method | Path | What it does |
| --- | --- | --- |
| POST | `/posts/{id}/unpublish` | Delete the status (for a thread: the first one) |
| GET | `/accounts/{id}/health` | Whether the token still works |

Deletes are queued for a person while approval applies to the API key, the organization or the account (202 `pending_approval`; MCP
`manage_published_post` returns the queued action).

## Mastodon's rules to know

- **Rate limits**: when a server answers 429, Postgale retries after the reset time it gives.
- **Media processing**: videos are processed by your server; if that takes longer than two minutes, the post is retried later.
- **Analytics**: favourites, boosts and replies are not collected yet, so post and account analytics answer `not_supported`.
