OpenTweet API Docs
Create, schedule, publish posts and access analytics for X/Twitter programmatically. Simple REST API with Bearer token auth.
Quick Start
Base URL
https://opentweet.ioAuthentication
All endpoints require an API key. Get yours from . You can pass the key in either header:
# Option 1: Authorization header
curl -H "Authorization: Bearer ot_your_api_key" \
https://opentweet.io/api/v1/me
# Option 2: X-API-Key header
curl -H "X-API-Key: ot_your_api_key" \
https://opentweet.io/api/v1/meRate Limits
Pro Plan
Advanced Plan
Rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) are included in 429 rate-limit error responses.
Endpoints
/api/v1/accountsList Connected X Accounts
Returns all X accounts connected to your OpenTweet account. Use the account "id" as x_account_id when creating or listing posts for a specific account.
Response
{
"accounts": [
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"x_handle": "@primary_account",
"x_name": "Primary Account",
"is_primary": true,
"nickname": null,
"x_subscription_type": "Premium+",
"connected_at": "2026-01-15T10:00:00Z",
"profile_image_url": "https://pbs.twimg.com/..."
},
{
"id": "65f1a2b3c4d5e6f7a8b9c0d2",
"x_handle": "@secondary_account",
"x_name": "Secondary Account",
"is_primary": false,
"nickname": "Brand",
"x_subscription_type": "Pro",
"connected_at": "2026-02-20T14:00:00Z",
"profile_image_url": "https://pbs.twimg.com/..."
}
]
}⚠ Pro plan supports 1 account, Advanced up to 3, Agency up to 10.
⚠ Use the "id" field as x_account_id in other endpoints to target a specific account.
/api/v1/meVerify Connection & Check Limits
Verify your API key works and check your subscription status, daily post limits, and post counts. Call this first before scheduling or publishing.
Response
{
"authenticated": true,
"subscription": {
"has_access": true,
"status": "active",
"is_trialing": false,
"trial_ends_at": null,
"current_period_ends_at": "2026-04-01T00:00:00Z"
},
"limits": {
"can_post": true,
"posts_published_today": 3,
"remaining_posts_today": 17,
"daily_limit": 20
},
"stats": {
"total_posts": 142,
"scheduled_posts": 8,
"posted_posts": 120,
"draft_posts": 14
}
}⚠ If subscription.has_access is false, you cannot schedule or publish posts.
⚠ limits is null when the user has no active subscription.
/api/v1/postsList Posts
Retrieve a paginated list of your posts. Filter by status to see only scheduled, posted, draft, or failed posts.
Query Parameters
| Name | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1) |
| limit | integer | Posts per page (default: 20, max: 100) |
| status | string | Filter: "scheduled", "posted", "draft", or "failed" |
| x_account_id | string | Filter by X account ID (for multi-account users) |
Response
{
"posts": [
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"x_account_id": "65f1a2b3c4d5e6f7a8b9c0d1",
"category": "General",
"text": "Just shipped a new feature!",
"scheduled_date": "2026-03-01T10:00:00Z",
"posted": false,
"posted_date": null,
"failed": false,
"failed_reason": null,
"is_thread": false,
"thread_tweets": null,
"media_urls": null,
"thread_media": null,
"community_id": null,
"share_with_followers": false,
"x_post_id": null,
"review_status": "approved",
"created_at": "2026-02-16T08:30:00Z",
"updated_at": "2026-02-16T08:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 142,
"pages": 8
}
}/api/v1/postsCreate Post(s)
Create a single post, a thread, or bulk create up to 50 posts at once. Include scheduled_date to schedule, or publish_now to post to X immediately.
Request Body
Including scheduled_date requires an active subscription and counts toward daily limits. Omit it to save as a draft. Use publish_now: true to post to X immediately (single post only, cannot combine with scheduled_date or bulk posts). All dates must be in the future (ISO 8601). Use x_account_id to target a specific X account (multi-account users).
// Single post (draft)
{
"text": "Just shipped a new feature!",
"category": "Product"
}
// Single post with schedule
{
"text": "Just shipped a new feature!",
"category": "Product",
"scheduled_date": "2026-03-01T10:00:00Z"
}
// Publish immediately to X
{
"text": "Hello from the API!",
"publish_now": true
}
// Post with media
{
"text": "Check out this screenshot!",
"media_urls": ["https://your-uploaded-media-url.jpg"],
"scheduled_date": "2026-03-01T10:00:00Z"
}
// Post to X Community
{
"text": "Shared with the community!",
"community_id": "1234567890",
"share_with_followers": true
}
// Post to a specific X account (multi-account)
{
"text": "Hello from my second account!",
"x_account_id": "65f1a2b3c4d5e6f7a8b9c0d2",
"publish_now": true
}
// Thread with per-tweet media
{
"text": "Here's what I learned building a SaaS in 2026:",
"is_thread": true,
"thread_tweets": [
"1/ Start with a problem you have yourself",
"2/ Ship fast, iterate faster",
"3/ Your first 10 users matter more than the next 1000"
],
"thread_media": [["https://img1.jpg"], [], ["https://img3.jpg"]],
"scheduled_date": "2026-03-01T10:00:00Z"
}
// Bulk create (up to 50)
{
"posts": [
{ "text": "Monday motivation", "scheduled_date": "2026-03-02T09:00:00Z" },
{ "text": "Tuesday tip", "scheduled_date": "2026-03-03T14:00:00Z" }
],
"x_account_id": "optional — applies to all posts if not set per-post"
}Response
{
"success": true,
"count": 1,
"posts": [
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"text": "Just shipped a new feature!",
"category": "Product",
"scheduled_date": "2026-03-01T10:00:00Z",
"is_thread": false,
"thread_tweets": null,
"media_urls": null,
"thread_media": null,
"community_id": null,
"share_with_followers": false,
"created_at": "2026-02-16T08:30:00Z"
}
]
}⚠ Returns 201 on success.
⚠ publish_now requires an active subscription and counts toward daily limits.
⚠ When publish_now succeeds, response includes x_post_id, posted: true, and posted_date.
⚠ When publish_now fails to post to X, returns 502 with the post ID (post is saved but not published).
/api/v1/posts/:idGet Single Post
Retrieve details of a specific post by its ID.
Response
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"category": "General",
"text": "Just shipped a new feature!",
"scheduled_date": "2026-03-01T10:00:00Z",
"posted": false,
"posted_date": null,
"failed": false,
"failed_reason": null,
"is_thread": false,
"thread_tweets": null,
"media_urls": null,
"thread_media": null,
"community_id": null,
"share_with_followers": false,
"x_post_id": null,
"review_status": "approved",
"created_at": "2026-02-16T08:30:00Z",
"updated_at": "2026-02-16T08:30:00Z"
}⚠ Same response shape as individual post in List Posts.
/api/v1/posts/:idUpdate Post
Update a draft or scheduled post. You cannot update posts that have already been published.
Request Body
All fields are optional. Only include the fields you want to update. Setting scheduled_date requires an active subscription. Set scheduled_date to null to unschedule (convert back to draft).
{
"text": "Updated tweet text",
"category": "Marketing",
"is_thread": false,
"thread_tweets": [],
"media_urls": [],
"thread_media": [],
"community_id": "1234567890",
"share_with_followers": true,
"scheduled_date": "2026-03-01T10:00:00Z"
}Response
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"text": "Updated tweet text",
"category": "Marketing",
"scheduled_date": "2026-03-01T10:00:00Z",
"is_thread": false,
"thread_tweets": null,
"media_urls": [],
"thread_media": null,
"community_id": "1234567890",
"share_with_followers": true,
"updated_at": "2026-02-16T09:00:00Z"
}⚠ Returns 400 if the post has already been published.
⚠ Setting scheduled_date counts toward daily limits.
/api/v1/posts/:idDelete Post
Permanently delete a post.
Response
{
"success": true,
"message": "Post deleted"
}⚠ Deleting a published post only removes it from OpenTweet. It does not delete the tweet from X/Twitter.
/api/v1/posts/:id/scheduleSchedule Post
Set or update the scheduled publish time for a post. Requires an active subscription.
Request Body
Date must be in the future. Use ISO 8601 format.
{
"scheduled_date": "2026-03-01T10:00:00Z"
}Response
{
"success": true,
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"text": "Just shipped a new feature!",
"scheduled_date": "2026-03-01T10:00:00Z",
"message": "Post scheduled successfully"
}⚠ Requires active subscription.
⚠ Counts toward daily post limits.
⚠ Returns 400 if the post has already been published.
/api/v1/posts/:id/publishPublish Immediately
Publish a post to X/Twitter right now. No request body needed. Requires an active subscription.
Response
{
"success": true,
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"text": "Just shipped a new feature!",
"x_post_id": "1234567890123456789",
"posted_date": "2026-02-16T10:00:00Z",
"message": "Post published successfully"
}⚠ Requires active subscription.
⚠ Counts toward daily post limits.
⚠ Returns 400 if the post has already been published.
⚠ Returns 502 if the X/Twitter API fails.
/api/v1/posts/batch-scheduleBatch Schedule Posts
Schedule multiple existing posts at once (up to 50). Requires an active subscription.
Request Body
community_id, share_with_followers, and x_account_id are optional and apply to all posts in the batch. All scheduled_date values must be in the future (ISO 8601).
{
"schedules": [
{ "post_id": "65f1a2b3c4d5e6f7a8b9c0d1", "scheduled_date": "2026-03-02T09:00:00Z" },
{ "post_id": "65f1a2b3c4d5e6f7a8b9c0d2", "scheduled_date": "2026-03-03T14:00:00Z" }
],
"community_id": "1234567890",
"share_with_followers": true,
"x_account_id": "optional — target X account"
}Response
{
"success": true,
"message": "2 post(s) scheduled",
"scheduled": [
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"text": "Monday motivation",
"category": "General",
"scheduled_date": "2026-03-02T09:00:00Z",
"is_thread": false
}
],
"errors": [
{ "post_id": "65f1a2b3c4d5e6f7a8b9c0d2", "error": "Post not found" }
]
}⚠ Requires active subscription.
⚠ Counts toward daily post limits.
⚠ Maximum 50 posts per batch.
⚠ errors array is only present if some posts failed to schedule.
/api/v1/uploadUpload Media
Upload an image or video to use in posts. Returns a URL to include in media_urls or thread_media when creating or updating posts.
Request Body
Use Content-Type: multipart/form-data. Supported formats: JPG, PNG, GIF, WebP, MP4, MOV. Max size: 5MB for images, 20MB for videos.
// multipart/form-data
// field: file (the image or video file)
curl -X POST https://opentweet.io/api/v1/upload \
-H "Authorization: Bearer ot_your_key" \
-F "file=@screenshot.png"Response
{
"url": "https://your-cdn-url.com/uploaded-file.png"
}⚠ Upload media first, then use the returned URL in media_urls or thread_media when creating/updating posts.
/api/v1/analytics/overviewAccount Overview
Returns comprehensive account-level analytics including posting stats, streaks, trends, and category breakdown.
Response
{
"stats": {
"total_posts_created": 142,
"total_posts_published": 120,
"total_posts_scheduled": 8,
"publishing_rate": 85,
"total_active_days": 45,
"avg_posts_per_week": 7.2,
"most_active_day": "monday",
"most_active_hour": 9,
"total_characters_written": 28400,
"total_threads_created": 12,
"total_media_posts": 34
},
"streaks": {
"current": 5,
"longest": 14,
"last_posted_date": "2026-02-26T10:00:00Z",
"streak_start_date": "2026-02-22T09:00:00Z"
},
"trends": {
"this_week": 7,
"last_week": 5,
"this_month": 24,
"last_month": 18,
"best_month": { "month": "2026-01", "count": 32 }
},
"categories": [
{ "name": "Product", "count": 45, "percentage": 32 }
],
"recent_activity": {
"last_7_days": [3, 2, 4, 1, 3, 2, 5],
"last_30_days": [2, 3, 1, ...]
}
}/api/v1/analytics/tweetsTweet Engagement Metrics
Returns engagement metrics for published tweets including likes, retweets, impressions, and engagement rates. Requires Advanced plan.
Query Parameters
| Name | Type | Description |
|---|---|---|
| period | integer or "all" | Number of days (7-365) or "all" for all time (default: 30) |
| x_account_id | string | Filter by X account ID (for multi-account users) |
Response
{
"has_x_account": true,
"has_data": true,
"sync_status": {
"last_synced_at": "2026-02-26T08:00:00Z",
"total_tweets_synced": 120
},
"summary": {
"total_tracked_tweets": 85,
"avg_engagement_rate": 3.2,
"avg_impressions": 1250,
"avg_likes": 15.4,
"avg_retweets": 3.1,
"avg_replies": 2.8,
"total_impressions": 106250,
"total_engagements": 3400
},
"top_performers": [
{
"x_post_id": "1234567890",
"text_preview": "Just shipped...",
"content_type": "text",
"posted_at": "2026-02-20T10:00:00Z",
"likes": 45, "retweets": 12, "replies": 8,
"quotes": 3, "impressions": 5200,
"bookmarks": 6, "engagement_rate": 5.1
}
],
"worst_performers": [],
"content_type_stats": [],
"engagement_timeline": [],
"best_engagement_hours": [],
"best_engagement_days": []
}⚠ Requires Advanced plan. Returns 403 on Pro plan.
⚠ Returns has_x_account: false if no X account is connected.
⚠ Returns has_data: false if no engagement data has been synced yet.
/api/v1/analytics/best-timesBest Posting Times
Analyzes your recent publishing patterns to find optimal posting times based on your last 15 published posts.
Response
{
"has_enough_data": true,
"total_posts_analyzed": 15,
"hour_distribution": [
{ "hour": 9, "label": "9AM", "count": 5 }
],
"day_distribution": [
{ "day": "Monday", "short_day": "Mon", "count": 4 }
],
"best_hours": [
{ "hour": 9, "label": "9AM", "count": 5 }
],
"best_days": [
{ "day": "Monday", "count": 4 }
],
"summary": "Most active days: Monday, Wednesday. Best posting hours: 9AM, 2PM"
}⚠ Requires at least 3 published posts. Returns has_enough_data: false otherwise.
Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (new post) |
| 400 | Bad request (validation error) |
| 401 | Unauthorized (invalid API key) |
| 403 | Forbidden (subscription required) |
| 404 | Post not found |
| 429 | Rate limit or daily post limit exceeded |
| 500 | Server error |
| 502 | X/Twitter API error |
Error Response Shape
{
"error": "Human-readable error message",
"details": ["Array of specific validation errors (optional)"],
"remaining": 0 // Only on 429 responses: remaining posts/requests allowed
}Common Workflows
Verify your connection
curl -H "Authorization: Bearer ot_your_key" \
https://opentweet.io/api/v1/meCheck authenticated is true and subscription.has_access is true.
Create and schedule a tweet (one step)
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{
"text": "Just shipped a new feature!",
"scheduled_date": "2026-03-01T10:00:00Z"
}'Post immediately (one step)
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{"text": "Hello from the API!", "publish_now": true}'Or use the two-step approach:
# Step 1: Create a draft
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{"text": "Hello from the API!"}'
# Step 2: Publish it (replace POST_ID with the id from step 1)
curl -X POST https://opentweet.io/api/v1/posts/POST_ID/publish \
-H "Authorization: Bearer ot_your_key"Schedule a week of content
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{
"posts": [
{"text": "Monday motivation", "scheduled_date": "2026-03-02T09:00:00Z"},
{"text": "Tuesday tip", "scheduled_date": "2026-03-03T14:00:00Z"},
{"text": "Wednesday wins", "scheduled_date": "2026-03-04T11:00:00Z"},
{"text": "Thursday thoughts", "scheduled_date": "2026-03-05T16:00:00Z"},
{"text": "Friday feels", "scheduled_date": "2026-03-06T10:00:00Z"}
]
}'Upload media and post with image
# Step 1: Upload the image
curl -X POST https://opentweet.io/api/v1/upload \
-H "Authorization: Bearer ot_your_key" \
-F "file=@screenshot.png"
# Returns: { "url": "https://..." }
# Step 2: Create post with the media URL
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{
"text": "Check out this screenshot!",
"media_urls": ["https://returned-url-from-upload"],
"scheduled_date": "2026-03-01T10:00:00Z"
}'Post to a specific X account (multi-account)
# Step 1: List your connected accounts
curl -H "Authorization: Bearer ot_your_key" \
https://opentweet.io/api/v1/accounts
# Returns accounts with their IDs
# Step 2: Create a post targeting a specific account
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello from my second account!",
"x_account_id": "ACCOUNT_ID_FROM_STEP_1",
"publish_now": true
}'If you omit x_account_id, posts go through your primary account.
Check your analytics
# Account overview (stats, streaks, trends)
curl -H "Authorization: Bearer ot_your_key" \
https://opentweet.io/api/v1/analytics/overview
# Best posting times
curl -H "Authorization: Bearer ot_your_key" \
https://opentweet.io/api/v1/analytics/best-times
# Tweet engagement (Advanced plan only)
curl -H "Authorization: Bearer ot_your_key" \
"https://opentweet.io/api/v1/analytics/tweets?period=30"Using OpenClaw?
Install the official skill and your agent handles everything above automatically.
clawhub install opentweet-x-poster
export OPENTWEET_API_KEY="ot_your_key"
openclaw "schedule 5 tweets about our launch for this week"Your agent can also read the plain-text API docs directly at /api/v1/docs.
Ready to build?
Get your API key and start posting in under 3 minutes.
API included in every plan. No extra charge.