Publishing API

The Publishing API (POST /v2/posts) is the core endpoint for publishing content to Facebook, Instagram, Threads, TikTok, and YouTube.

Endpoint

POST https://boring.aiagent-me.com/v2/posts

Authentication

Required header:

boring-api-key: boring_xxxxxxxxxxxxx

See API Keys for how to generate a key.

Request Format

Headers

POST /v2/posts HTTP/1.1
Host: boring.aiagent-me.com
Content-Type: application/json
boring-api-key: boring_xxxxxxxxxxxxx

Request Body

{
  "post": {
    "accountId": "uuid-string",
    "content": {
      "text": "Post content" | ["Thread post 1", "Thread post 2"],
      "mediaUrls": ["https://example.com/image.jpg"] | "https://example.com/image.jpg",
      "platform": "facebook" | "instagram" | "threads" | "tiktok" | "youtube"
    },
    "target": {
      "targetType": "facebook" | "instagram" | "threads" | "tiktok" | "youtube"
    }
  }
}

Parameters

Field Type Required Description
post Object Yes The post object
post.accountId String (UUID) Yes Account ID from dashboard
post.content Object Yes Content object
post.content.text String or Array Conditional Post text/caption (required for Facebook/Instagram, optional for Threads text-only). Array format creates Threads thread.
post.content.mediaUrls Array or String Conditional Media URLs. Can be array or single string. Required for Instagram.
post.content.platform String Yes Platform: "facebook", "instagram", "threads", "tiktok", or "youtube"
post.target Object Yes Target object
post.target.targetType String Yes Same as platform

Platform-Specific Behavior

Facebook

Supported post types:

Media Count Post Type Example
0 Text-only mediaUrls: []
1 image Single photo mediaUrls: ["image.jpg"]
2-10 images Album mediaUrls: ["img1.jpg", "img2.jpg", ...]
1 video Video mediaUrls: ["video.mp4"]

Example:

{
  "post": {
    "accountId": "fb-page-account-id",
    "content": {
      "text": "Check out our new product line!",
      "mediaUrls": [
        "https://cdn.example.com/product1.jpg",
        "https://cdn.example.com/product2.jpg",
        "https://cdn.example.com/product3.jpg"
      ],
      "platform": "facebook"
    },
    "target": {
      "targetType": "facebook"
    }
  }
}

Instagram

Supported post types:

Media Count Post Type Example
1 image Single photo mediaUrls: ["image.jpg"]
2-10 images Carousel mediaUrls: ["img1.jpg", ...]
1 video Reels mediaUrls: ["video.mp4"]

Note: Instagram requires media. Text-only posts are not supported.

Example:

{
  "post": {
    "accountId": "instagram-account-id",
    "content": {
      "text": "New collection is here! Swipe to see all colors 🎨\n\n#fashion #style #newcollection",
      "mediaUrls": [
        "https://cdn.example.com/color1.jpg",
        "https://cdn.example.com/color2.jpg",
        "https://cdn.example.com/color3.jpg"
      ],
      "platform": "instagram"
    },
    "target": {
      "targetType": "instagram"
    }
  }
}

Threads

Supported post types:

Text Format Media Count Post Type Example
String 0 Text-only text: "Hello", mediaUrls: []
String 1 image Single photo text: "Hi", mediaUrls: ["img.jpg"]
String 2-20 images Carousel text: "Pics", mediaUrls: ["img1.jpg", ...]
String 1 video Video text: "Video", mediaUrls: ["video.mp4"]
Array 0-1 media Thread (multi-post) text: ["Post 1", "Post 2"]

Single Post Example:

{
  "post": {
    "accountId": "threads-account-id",
    "content": {
      "text": "Just shipped a new feature! πŸš€",
      "mediaUrls": [],
      "platform": "threads"
    },
    "target": {
      "targetType": "threads"
    }
  }
}

Thread Example:

{
  "post": {
    "accountId": "threads-account-id",
    "content": {
      "text": [
        "🧡 Let me explain how our API works (thread)",
        "First, you sign in with Google and connect your social accounts.",
        "Then, you generate an API key from the Settings page.",
        "Finally, you make POST requests to /v2/posts with your content.",
        "It's that simple! Questions? Reply below πŸ‘‡"
      ],
      "mediaUrls": [],
      "platform": "threads"
    },
    "target": {
      "targetType": "threads"
    }
  }
}

TikTok

Supported post types:

Media Count Post Type Example
1 video Video post mediaUrls: ["video.mp4"]

Notes:

Video Upload Example:

{
  "post": {
    "accountId": "tiktok-account-id",
    "content": {
      "text": "Check out this amazing dance! πŸ’ƒ #dance #fyp #trending",
      "mediaUrls": ["https://storage.example.com/dance-video.mp4"],
      "platform": "tiktok"
    },
    "target": {
      "targetType": "tiktok"
    }
  }
}

Response:

{
  "success": true,
  "postSubmissionId": "ca5b8e50-9376-4fbb-b2fc-b619849e3574",
  "platform": "tiktok",
  "post_type": "video",
  "publish_id": "v_pub_file~v2-1.7577571164748499000",
  "status": "PUBLISH_COMPLETE",
  "message": "Post published successfully"
}

Important: For sandbox testing:

  1. Set your TikTok account to Private in TikTok app settings
  2. Disconnect and reconnect your TikTok account in Boring
  3. Videos will only be visible to you (SELF_ONLY privacy)

Response Format

Success Response

{
  "success": true,
  "message": "Post published successfully",
  "data": {
    "post_submission_id": "uuid-string",
    "post_id": "platform-specific-id",
    "post_type": "text|photo|album|carousel|video|reels|thread",
    "platform": "facebook|instagram|threads|tiktok|youtube",
    "page_name": "Account name or username",
    "published_at": "2025-01-20T10:30:00Z",

    // Platform-specific fields
    "photo_count": 3,          // For Facebook albums
    "item_count": 5,           // For Instagram carousels
    "thread_count": 7,         // For Threads threads
    "post_ids": ["id1", "id2"] // For Threads threads
  }
}

Field Descriptions

Field Description
post_submission_id Unique ID for this submission (UUID)
post_id Platform's post ID
post_type Type of post published
platform Platform name
page_name Account name or username
published_at ISO 8601 timestamp
photo_count Number of photos (Facebook albums)
item_count Number of items (Instagram carousels)
thread_count Number of posts in thread (Threads)
post_ids Array of all post IDs (Threads threads)

Error Response

{
  "success": false,
  "error": "InvalidAccountId",
  "message": "Account not found or does not belong to this user"
}

See Errors for all error codes.

Media URL Format

Supported Formats

Images:

Videos:

URL Requirements

Media URLs must be:

  1. Publicly accessible - No authentication required
  2. Direct file URLs - Not HTML pages
  3. HTTPS - HTTP URLs are upgraded automatically
  4. Valid - Return 200 status code
  5. Correct content-type - image/jpeg, video/mp4, etc.

URL Format Examples

Good:

βœ… https://cdn.example.com/images/photo.jpg
βœ… https://storage.googleapis.com/bucket/video.mp4
βœ… https://s3.amazonaws.com/bucket/image.png

Bad:

❌ http://example.com/photo.jpg (not HTTPS)
❌ https://example.com/photos/123 (not direct file URL)
❌ https://example.com/photo.jpg?auth=token (requires auth)
❌ file:///local/path/photo.jpg (local file)

Single String vs Array

The API accepts both formats for mediaUrls:

// Single string (converted to array internally)
"mediaUrls": "https://example.com/photo.jpg"

// Array (recommended)
"mediaUrls": ["https://example.com/photo.jpg"]

// Multiple items (must be array)
"mediaUrls": [
  "https://example.com/photo1.jpg",
  "https://example.com/photo2.jpg"
]

Recommendation: Always use array format for consistency.

Code Examples

Python

import requests
import os

API_URL = "https://boring.aiagent-me.com/v2/posts"
API_KEY = os.environ["BORING_API_KEY"]

def publish_post(account_id, platform, text, media_urls=[]):
    """Publish a post to social media"""

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

    data = {
        "post": {
            "accountId": account_id,
            "content": {
                "text": text,
                "mediaUrls": media_urls,
                "platform": platform
            },
            "target": {
                "targetType": platform
            }
        }
    }

    response = requests.post(API_URL, headers=headers, json=data)
    return response.json()

# Example usage
result = publish_post(
    account_id="your-account-id",
    platform="instagram",
    text="Check out this amazing sunset! πŸŒ… #photography",
    media_urls=["https://example.com/sunset.jpg"]
)

if result["success"]:
    print(f"Published! Post ID: {result['data']['post_id']}")
else:
    print(f"Error: {result['message']}")

JavaScript (Node.js)

const axios = require('axios');

const API_URL = "https://boring.aiagent-me.com/v2/posts";
const API_KEY = process.env.BORING_API_KEY;

async function publishPost(accountId, platform, text, mediaUrls = []) {
  const headers = {
    "boring-api-key": API_KEY,
    "Content-Type": "application/json"
  };

  const data = {
    post: {
      accountId: accountId,
      content: {
        text: text,
        mediaUrls: mediaUrls,
        platform: platform
      },
      target: {
        targetType: platform
      }
    }
  };

  try {
    const response = await axios.post(API_URL, data, { headers });
    return response.data;
  } catch (error) {
    console.error("API Error:", error.response?.data || error.message);
    throw error;
  }
}

// Example usage
publishPost(
  "your-account-id",
  "threads",
  "Just launched our new feature! πŸš€",
  []
).then(result => {
  if (result.success) {
    console.log(`Published! Post ID: ${result.data.post_id}`);
  }
});

cURL

curl -X POST https://boring.aiagent-me.com/v2/posts \
  -H "boring-api-key: $BORING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "post": {
      "accountId": "your-account-id",
      "content": {
        "text": "Hello from cURL!",
        "mediaUrls": ["https://example.com/image.jpg"],
        "platform": "facebook"
      },
      "target": {
        "targetType": "facebook"
      }
    }
  }'

Advanced Usage

Publishing to Multiple Platforms

def cross_platform_publish(accounts, text, media_urls):
    """Publish to multiple platforms simultaneously"""
    results = {}

    for platform, account_id in accounts.items():
        # Adapt content for each platform
        adapted_text = adapt_content(text, platform)
        adapted_media = adapt_media(media_urls, platform)

        result = publish_post(
            account_id=account_id,
            platform=platform,
            text=adapted_text,
            media_urls=adapted_media
        )

        results[platform] = result

    return results

# Usage
accounts = {
    "facebook": "fb-account-id",
    "instagram": "ig-account-id",
    "threads": "threads-account-id"
}

results = cross_platform_publish(
    accounts=accounts,
    text="Check out our new product!",
    media_urls=["https://example.com/product.jpg"]
)

Retry with Exponential Backoff

import time

def publish_with_retry(account_id, platform, text, media_urls, max_retries=3):
    """Publish with automatic retry on failure"""

    for attempt in range(max_retries):
        try:
            result = publish_post(account_id, platform, text, media_urls)

            if result["success"]:
                return result

            # Check if error is retryable
            if result.get("error") in ["RateLimitExceeded", "ServiceUnavailable"]:
                wait_time = 2 ** attempt  # Exponential backoff
                print(f"Retrying in {wait_time} seconds...")
                time.sleep(wait_time)
                continue
            else:
                # Non-retryable error
                return result

        except Exception as e:
            if attempt == max_retries - 1:
                raise
            time.sleep(2 ** attempt)

    return {"success": False, "error": "MaxRetriesExceeded"}

Batch Publishing

def batch_publish(posts):
    """Publish multiple posts with rate limiting"""
    results = []

    for i, post in enumerate(posts):
        result = publish_post(**post)
        results.append(result)

        # Rate limiting: wait 2 seconds between posts
        if i < len(posts) - 1:
            time.sleep(2)

    return results

# Usage
posts = [
    {"account_id": "acc1", "platform": "facebook", "text": "Post 1", "media_urls": []},
    {"account_id": "acc2", "platform": "instagram", "text": "Post 2", "media_urls": ["img.jpg"]},
    {"account_id": "acc3", "platform": "threads", "text": "Post 3", "media_urls": []}
]

results = batch_publish(posts)

Asynchronous Publishing

Some post types are processed asynchronously:

Post Type Processing Time
Text posts Instant
Photo posts 1-5 seconds
Carousels 5-15 seconds
Videos 30 seconds - 3 minutes
Threads 10-30 seconds per post

The API waits for processing to complete before returning. If it takes too long:

Monitoring and Logging

Check Publishing History

def get_recent_posts(limit=20):
    """Get recent published posts"""
    # Requires session authentication (dashboard)
    # Use this endpoint from your dashboard, not programmatically

    url = f"https://boring.aiagent-me.com/api/published-posts?limit={limit}"
    # This requires session cookie, not API key
    response = requests.get(url, cookies=session_cookies)
    return response.json()

Note: The publishing history endpoint requires dashboard authentication (session cookie), not an API key.

Log All Requests

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def publish_post_logged(account_id, platform, text, media_urls):
    """Publish with comprehensive logging"""

    logger.info(f"Publishing to {platform} (account: {account_id})")
    logger.debug(f"Text: {text[:100]}...")
    logger.debug(f"Media URLs: {media_urls}")

    result = publish_post(account_id, platform, text, media_urls)

    if result["success"]:
        logger.info(f"Success! Post ID: {result['data']['post_id']}")
    else:
        logger.error(f"Failed: {result['message']}")

    return result

Best Practices

  1. Validate before publishing - Check account IDs and media URLs
  2. Handle errors gracefully - Implement retry logic
  3. Monitor rate limits - Respect platform limits
  4. Test media URLs - Ensure they're accessible
  5. Log all requests - Track successes and failures
  6. Use environment variables - Never hardcode API keys
  7. Implement timeouts - Don't wait indefinitely
  8. Batch wisely - Don't overwhelm the API

Next Steps