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
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"
}
}
}
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:
- TikTok only supports video uploads (no photos or text-only)
- Caption is optional (max 2200 characters)
- Video max size: 4GB, max duration: 10 minutes
- Sandbox accounts require private TikTok profile
- Videos published with
SELF_ONLYprivacy in sandbox mode - Chunked upload automatically used for files β₯ 5MB
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:
- Set your TikTok account to Private in TikTok app settings
- Disconnect and reconnect your TikTok account in Boring
- 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:
- JPG / JPEG
- PNG
- WEBP (Threads only)
Videos:
- MP4
- MOV
URL Requirements
Media URLs must be:
- Publicly accessible - No authentication required
- Direct file URLs - Not HTML pages
- HTTPS - HTTP URLs are upgraded automatically
- Valid - Return 200 status code
- 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:
- The request may timeout (300 seconds for videos)
- Check publishing history to see if it succeeded
- Retry if not found after 5 minutes
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
- Validate before publishing - Check account IDs and media URLs
- Handle errors gracefully - Implement retry logic
- Monitor rate limits - Respect platform limits
- Test media URLs - Ensure they're accessible
- Log all requests - Track successes and failures
- Use environment variables - Never hardcode API keys
- Implement timeouts - Don't wait indefinitely
- Batch wisely - Don't overwhelm the API
Next Steps
- Errors - Learn about error handling
- Examples - See complete examples
- Platform Guides - Platform-specific details