PostPost

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?

FeaturePostPost APIMastodon API
AuthenticationSingle API keyOAuth 2.0 per instance
Instance supportmastodon.socialAny instance
Multi-platformPost to 11 platformsMastodon/Fediverse only
Setup time5 minutesVaries by instance
Media handlingAutomaticManual upload
FederationAutomaticAutomatic

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

TypeSupportedLimits
TextYes500 characters
ImagesYesJPEG, PNG, GIF, WebP, up to 4 per post
VideosYesMP4, WebM, MOV formats
VisibilityYesPublic 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 profileUrl field, but profileUrl is 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.js and 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.js to 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/mp4 to 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

ElementLimit
Post body500 characters (instance-configurable, some allow 5,000+)
Media description (alt text)1,500 characters

Media Limits

Media TypeMax SizeMax CountSupported Formats
Images16 MB4 per postJPEG, PNG, GIF, WebP
Videos~99 MB1 per postMP4, WebM, MOV
Video ConstraintLimit
DurationNo platform-enforced limit

Rate Limits

Limit TypeValue
Media uploads30 per 30 minutes
API requests300 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

On this page