Mastodon
Post to Mastodon and the Fediverse programmatically using the PostPost REST API. A simpler alternative to the official Mastodon API, Megalodon, or direct ActivityPub integration.
Mastodon API Overview
PostPost provides a unified REST API for publishing text posts and media content to Mastodon and the fediverse. Posts are published to the mastodon.social instance. No need to manage Mastodon OAuth flows, handle ActivityPub protocols, or set up your own Mastodon application.
Why Use PostPost Instead of Mastodon API / Megalodon?
| Feature | PostPost API | Mastodon API |
|---|---|---|
| Authentication | Single API key | OAuth 2.0 per instance |
| Instance support | mastodon.social | Any instance |
| Multi-platform | Post to 11 platforms | Mastodon/Fediverse only |
| Setup time | 5 minutes | Varies by instance |
| Media handling | Automatic | Manual upload |
| Federation | Automatic | Automatic |
Keywords: Mastodon API, Mastodon posting API, Fediverse API, ActivityPub API, post to Mastodon programmatically, Mastodon REST API, Mastodon developer API, Mastodon bot API, Mastodon automation API, toot API, Mastodon status API, decentralized social API
Platform ID Format
mastodon-{accountId}Where {accountId} is your Mastodon account ID assigned during account connection through the PostPost dashboard.
Requirements
- A Mastodon account on the mastodon.social instance connected through the PostPost dashboard (OAuth scopes requested:
read,write,push) - API key from PostPost
Supported Content
| Type | Supported | Limits |
|---|---|---|
| Text | Yes | 500 characters |
| Images | Yes | JPEG, PNG, GIF, WebP, up to 4 per post |
| Videos | Yes | MP4, WebM, MOV formats |
| Visibility | Yes | Public by default |
Visibility
Posts on Mastodon are set to public visibility by default when posted through PostPost. This means they will appear on your profile, in your followers' home timelines, and on the public federated timeline.
Examples
Post a Text Update
JavaScript (fetch)
const response = await fetch('https://api.postpost.dev/api/v1/create-post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
content: 'Hello fediverse! We just shipped a major update to our open-source project. Check out the changelog at https://example.com/changelog #opensource #fediverse',
platforms: ['mastodon-109876543210']
})
});
const data = await response.json();
console.log(data);
// Response: { "success": true, "postGroupId": "abc123..." }Python (requests)
import requests
response = requests.post(
'https://api.postpost.dev/api/v1/create-post',
headers={
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
json={
'content': 'Hello fediverse! We just shipped a major update to our open-source project. Check out the changelog at https://example.com/changelog #opensource #fediverse',
'platforms': ['mastodon-109876543210']
}
)
data = response.json()
print(data)
# Response: { "success": true, "postGroupId": "abc123..." }cURL
curl -X POST https://api.postpost.dev/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"content": "Hello fediverse! We just shipped a major update to our open-source project. Check out the changelog at https://example.com/changelog #opensource #fediverse",
"platforms": ["mastodon-109876543210"]
}'
# Response: { "success": true, "postGroupId": "abc123..." }Node.js (axios)
const axios = require('axios');
const response = await axios.post('https://api.postpost.dev/api/v1/create-post', {
content: 'Hello fediverse! We just shipped a major update to our open-source project. Check out the changelog at https://example.com/changelog #opensource #fediverse',
platforms: ['mastodon-109876543210']
}, {
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
}
});
console.log(response.data);
// Response: { "success": true, "postGroupId": "abc123..." }Post with Media
JavaScript (fetch)
const response = await fetch('https://api.postpost.dev/api/v1/create-post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
content: 'New feature alert: our dashboard now supports dark mode! Here is a side-by-side comparison. #ui #darkmode',
platforms: ['mastodon-109876543210']
})
});
const data = await response.json();
console.log(data);
// Response: { "success": true, "postGroupId": "abc123..." }Note: To attach media to a Mastodon post, first create the post, then upload media using the media upload workflow with the returned
postGroupId.
Python (requests)
import requests
response = requests.post(
'https://api.postpost.dev/api/v1/create-post',
headers={
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
json={
'content': 'New feature alert: our dashboard now supports dark mode! Here is a side-by-side comparison. #ui #darkmode',
'platforms': ['mastodon-109876543210']
}
)
data = response.json()
print(data)
# Response: { "success": true, "postGroupId": "abc123..." }cURL
curl -X POST https://api.postpost.dev/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"content": "New feature alert: our dashboard now supports dark mode! Here is a side-by-side comparison. #ui #darkmode",
"platforms": ["mastodon-109876543210"]
}'
# Response: { "success": true, "postGroupId": "abc123..." }Node.js (axios)
const axios = require('axios');
const response = await axios.post('https://api.postpost.dev/api/v1/create-post', {
content: 'New feature alert: our dashboard now supports dark mode! Here is a side-by-side comparison. #ui #darkmode',
platforms: ['mastodon-109876543210']
}, {
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
}
});
console.log(response.data);
// Response: { "success": true, "postGroupId": "abc123..." }Post with a Video
JavaScript (fetch)
const response = await fetch('https://api.postpost.dev/api/v1/create-post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
content: 'Quick demo of our new real-time collaboration feature. Multiple users editing the same document simultaneously!',
platforms: ['mastodon-109876543210']
})
});
const data = await response.json();
console.log(data);
// Response: { "success": true, "postGroupId": "abc123..." }Python (requests)
import requests
response = requests.post(
'https://api.postpost.dev/api/v1/create-post',
headers={
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
json={
'content': 'Quick demo of our new real-time collaboration feature. Multiple users editing the same document simultaneously!',
'platforms': ['mastodon-109876543210']
}
)
data = response.json()
print(data)
# Response: { "success": true, "postGroupId": "abc123..." }cURL
curl -X POST https://api.postpost.dev/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"content": "Quick demo of our new real-time collaboration feature. Multiple users editing the same document simultaneously!",
"platforms": ["mastodon-109876543210"]
}'
# Response: { "success": true, "postGroupId": "abc123..." }Node.js (axios)
const axios = require('axios');
const response = await axios.post('https://api.postpost.dev/api/v1/create-post', {
content: 'Quick demo of our new real-time collaboration feature. Multiple users editing the same document simultaneously!',
platforms: ['mastodon-109876543210']
}, {
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
}
});
console.log(response.data);
// Response: { "success": true, "postGroupId": "abc123..." }Platform Quirks
- mastodon.social only: New connections are limited to the mastodon.social instance (the OAuth flow uses a hardcoded instance URL). The test-connection validator attempts to extract the instance URL from the connection's
profileUrlfield, butprofileUrlis never set during Mastodon connection creation, so it always falls back to mastodon.social. Support for other instances may be added in the future. - Public by default: All posts made through PostPost are published with public visibility. They will appear on the federated timeline.
- Up to 4 images: A maximum of 4 images can be attached to a single post. PostPost enforces this limit at scheduling time via
postValidationService.jsand will reject posts that exceed it before they reach the Mastodon API. - Image formats: Mastodon accepts JPEG, PNG, GIF, and WebP natively. PostPost validates image formats via
postValidationService.jsto ensure only supported formats are attached. No format conversion is performed — all supported formats are passed through as-is. - MP4, WebM, and MOV for videos: Mastodon accepts MP4, WebM, and MOV video formats. PostPost accepts all three as input, but the scheduler currently reports the MIME type as
video/mp4to Mastodon regardless of the actual format. MP4 uploads work correctly; WebM and MOV files may experience processing issues due to the mismatched MIME type. - 500-character limit: Mastodon enforces a strict 500-character limit. PostPost will return an error if your content exceeds this. Unlike X/Twitter and Threads, Mastodon does not auto-thread.
- Hashtags: Hashtags in Mastodon are part of the post body and count toward the character limit. They become clickable and searchable on the platform.
- Content warnings: Mastodon supports content warnings (CW), but this feature is not currently available through the PostPost API.
- Federation delay: Because Mastodon is federated, posts may take a few seconds to propagate to other instances in the fediverse.
API Limits
Text Limits
| Element | Limit |
|---|---|
| Post body | 500 characters (instance-configurable, some allow 5,000+) |
| Media description (alt text) | 1,500 characters |
Media Limits
| Media Type | Max Size | Max Count | Supported Formats |
|---|---|---|---|
| Images | 16 MB | 4 per post | JPEG, PNG, GIF, WebP |
| Videos | ~99 MB | 1 per post | MP4, WebM, MOV |
| Video Constraint | Limit |
|---|---|
| Duration | No platform-enforced limit |
Rate Limits
| Limit Type | Value |
|---|---|
| Media uploads | 30 per 30 minutes |
| API requests | 300 per 5 minutes |
Additional Notes
- Character limits vary by Mastodon instance; mastodon.social uses 500 characters, but some instances allow 5,000+
- PostPost currently connects to mastodon.social only (posting is hardcoded to this instance)
- Unlike X/Twitter and Threads, Mastodon does not support auto-threading
- Max image count (4) and video count (1) limits are enforced by PostPost at scheduling time via
postValidationService.js
Bluesky
Post to Bluesky programmatically using the PostPost REST API. A simpler alternative to the AT Protocol (atproto) SDK or direct Bluesky API integration.
Telegram
Post to Telegram channels and groups programmatically using the PostPost REST API. A simpler alternative to the Telegram Bot API, Telethon, Pyrogram, or MTProto libraries.