X (Twitter) Publishing

Learn how to connect X (Twitter) accounts and post tweets with text, images, and videos.

⚠️ Ultra plan only. Connecting an X account is available on every plan, but publishing to X requires the Ultra plan (priced at 2× Pro). Ultra allows up to 60 X posts per month.

Connecting X Account

Prerequisites

Connection Steps

  1. Click the "Connect X (Twitter)" button on the dashboard
  2. Authorize the app on X (Twitter)
  3. Review and grant the required permissions:
    • tweet.read - Read tweets
    • tweet.write - Post and delete tweets
    • users.read - Read your profile information
    • media.write - Upload images and videos
    • offline.access - Maintain long-term access
  4. Click "Authorize app" to complete the connection

Your X account will now appear in the Authorized Accounts list with:

Token Information

Supported Content Types

X (Twitter) supports tweets with various media types:

Feature Description Required Limits
Text Tweet text No* Max 280 characters
Images Photo attachments No Max 4 images, 5MB each
Video Video attachment No Max 1 video, 512MB

*At least text or media is required

Publishing Examples

1. Text-Only Tweet

{
  "post": {
    "accountId": "your-x-account-id",
    "content": {
      "text": "Hello from Boring API! 🚀 This is a text-only tweet. #API #Automation",
      "mediaUrls": [],
      "platform": "x"
    },
    "target": {
      "targetType": "x"
    }
  }
}

Character Limit: Text is automatically truncated to 280 characters if longer.

2. Tweet with Images (1-4 images)

{
  "post": {
    "accountId": "your-x-account-id",
    "content": {
      "text": "Check out these amazing photos! 📸\n\n#Photography #Travel",
      "mediaUrls": [
        "https://storage.example.com/photo1.jpg",
        "https://storage.example.com/photo2.jpg",
        "https://storage.example.com/photo3.jpg"
      ],
      "platform": "x"
    },
    "target": {
      "targetType": "x"
    }
  }
}

Image Requirements:

3. Tweet with Video

{
  "post": {
    "accountId": "your-x-account-id",
    "content": {
      "text": "New video tutorial! 🎥 Learn how to use our API in 5 minutes.\n\n#Tutorial #API #DevTools",
      "mediaUrls": ["https://storage.example.com/tutorial.mp4"],
      "platform": "x"
    },
    "target": {
      "targetType": "x"
    }
  }
}

Video Requirements:

Note: Only one video per tweet. You cannot mix images and videos.

API Request Format

Full Example with Python

import requests

API_URL = "https://boring.aiagent-me.com/v2/posts"
API_KEY = "boring_xxxxxxxxxxxxx"
ACCOUNT_ID = "your-x-account-id"

# Tweet with images
post_data = {
    "post": {
        "accountId": ACCOUNT_ID,
        "content": {
            "text": "Exciting news! 🎉 We just launched our new feature. Check it out!\n\n#ProductLaunch #Innovation",
            "mediaUrls": [
                "https://storage.googleapis.com/my-bucket/feature-screenshot.jpg"
            ],
            "platform": "x"
        },
        "target": {
            "targetType": "x"
        }
    }
}

headers = {
    "boring-api-key": API_KEY,
    "Content-Type": "application/json"
}

response = requests.post(API_URL, headers=headers, json=post_data)
result = response.json()

if result.get("success"):
    print(f"Tweet posted successfully!")
    print(f"Tweet ID: {result['tweet_id']}")
    print(f"Tweet URL: {result['tweet_url']}")
else:
    print(f"Tweet failed: {result.get('error')}")

Success Response

{
  "success": true,
  "message": "Post published successfully",
  "postSubmissionId": "uuid-here",
  "platform": "x",
  "post_type": "photo",
  "tweet_id": "1234567890123456789",
  "tweet_url": "https://x.com/i/status/1234567890123456789",
  "media_count": 1
}

Media Upload Process

Media and tweets are published with each connected account's own OAuth 2.0 token:

  1. Download Media: Media files are downloaded from provided URLs
  2. OAuth 2.0 Upload: Media is uploaded to X using the connected account's OAuth 2.0 token (v2 /2/media/upload, requires the media.write scope), so the media is owned by the posting account
  3. Processing: X processes the media (especially for videos)
  4. OAuth 2.0 Tweet: Tweet is created with the uploaded media (v2 /2/tweets)

Video Processing

Videos require additional processing time:

[X] Uploading video in chunks...
[X] Upload progress: 33%
[X] Upload progress: 67%
[X] Upload progress: 100%
[X] Video processing status: pending
[X] Video processing status: processing
[X] Video processing status: succeeded
[X] Tweet posted successfully!

Typical processing times:

Character Limit and Text Handling

280 Character Limit

X enforces a strict 280 character limit. Boring automatically handles this:

# Text longer than 280 characters
text = "This is a very long tweet..." * 50  # 1000+ characters

# Boring automatically truncates to 280 characters
post_data = {
    "post": {
        "accountId": ACCOUNT_ID,
        "content": {
            "text": text,  # Will be truncated
            "platform": "x"
        },
        "target": {"targetType": "x"}
    }
}

Result: Text is truncated to first 280 characters.

Unicode and Emojis

Emojis and special characters count differently:

Example:

"Hello 👋 World 🌍" = 14 characters (2 for each emoji)
"Check out https://example.com/very/long/url" = ~30 characters (URL = 23 chars)

API Request Examples

cURL Example

curl -X POST https://boring.aiagent-me.com/v2/posts \
  -H "boring-api-key: boring_xxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "post": {
      "accountId": "your-x-account-id",
      "content": {
        "text": "Testing the Boring API with X! 🚀 #API #Testing",
        "mediaUrls": [],
        "platform": "x"
      },
      "target": {
        "targetType": "x"
      }
    }
  }'

JavaScript/Node.js Example

const axios = require('axios');

const API_URL = 'https://boring.aiagent-me.com/v2/posts';
const API_KEY = 'boring_xxxxxxxxxxxxx';
const ACCOUNT_ID = 'your-x-account-id';

async function postTweet(text, mediaUrls = []) {
    const postData = {
        post: {
            accountId: ACCOUNT_ID,
            content: {
                text: text,
                mediaUrls: mediaUrls,
                platform: 'x'
            },
            target: {
                targetType: 'x'
            }
        }
    };

    try {
        const response = await axios.post(API_URL, postData, {
            headers: {
                'boring-api-key': API_KEY,
                'Content-Type': 'application/json'
            }
        });

        console.log('Tweet posted:', response.data);
        return response.data;
    } catch (error) {
        console.error('Error posting tweet:', error.response?.data || error.message);
        throw error;
    }
}

// Post a text-only tweet
postTweet('Hello from Node.js! 👋 #NodeJS #API');

// Post a tweet with an image
postTweet(
    'Check out this beautiful sunset! 🌅 #Photography',
    ['https://storage.example.com/sunset.jpg']
);

Troubleshooting

Common Errors

Error: "Account is not an X (Twitter) account"

Error: "Too many media files"

Error: "Cannot mix images and videos"

Error: "Failed to upload media"

Error: "Video processing failed"

Error: "Token refresh failed"

Best Practices

  1. Keep tweets concise - 280 characters is the limit
  2. Use hashtags strategically - 2-3 relevant hashtags maximum
  3. Optimize images - Use high-quality JPG or PNG files
  4. Compress videos - Keep under 50MB for faster upload
  5. Test URLs first - Verify media URLs are accessible
  6. Monitor rate limits - Space out tweets to avoid rate limiting
  7. Check tweet preview - Preview in dashboard before publishing

Rate Limits

X API has the following rate limits per user:

Boring handles rate limit errors gracefully and returns appropriate error messages.

Boring Ultra plan limit: X publishing is capped at 60 posts per month per user (resets at the start of each month, counted across all your connected X accounts). Exceeding it returns X_MONTHLY_LIMIT_EXCEEDED (HTTP 429).

Publishing History

View all your tweets in the dashboard:

  1. Sign in to Boring Dashboard
  2. Scroll to "Publish History" section
  3. Filter by platform: X

Each entry shows:

Advanced Features

Multiple X Accounts

You can connect multiple X accounts to the same Boring account:

# Account 1: Personal (@john_personal)
personal_account_id = "account-id-1"

# Account 2: Business (@john_business)
business_account_id = "account-id-2"

# Post to personal account
post_to_account(personal_account_id, "Personal tweet! 👋")

# Post to business account
post_to_account(business_account_id, "Business announcement! 📢")

Batch Posting

Post multiple tweets programmatically:

tweets = [
    {"text": "Tweet 1: Introduction 👋", "media": []},
    {"text": "Tweet 2: Key features 🚀", "media": ["feature.jpg"]},
    {"text": "Tweet 3: Final thoughts 💭", "media": []}
]

for tweet in tweets:
    post_data = {
        "post": {
            "accountId": ACCOUNT_ID,
            "content": {
                "text": tweet["text"],
                "mediaUrls": tweet["media"],
                "platform": "x"
            },
            "target": {"targetType": "x"}
        }
    }

    response = requests.post(API_URL, headers=headers, json=post_data)
    print(f"Posted: {tweet['text']} - {response.json()}")

    # Wait between tweets to avoid rate limits
    time.sleep(60)  # 60 seconds delay

Security Notes

Quick Reference

Minimal Tweet (Text Only)

{
  "post": {
    "accountId": "your-account-id",
    "content": {
      "text": "Hello World! 🌍",
      "platform": "x"
    },
    "target": {"targetType": "x"}
  }
}

Tweet with Image

{
  "post": {
    "accountId": "your-account-id",
    "content": {
      "text": "Check this out! 📸",
      "mediaUrls": ["https://example.com/image.jpg"],
      "platform": "x"
    },
    "target": {"targetType": "x"}
  }
}

Tweet with Video

{
  "post": {
    "accountId": "your-account-id",
    "content": {
      "text": "Watch this! 🎥",
      "mediaUrls": ["https://example.com/video.mp4"],
      "platform": "x"
    },
    "target": {"targetType": "x"}
  }
}

Next Steps