Schedule tweets programmatically
with a single REST call
POST a scheduled_date in ISO 8601 and OpenTweet publishes at that time. No cron to run, no OAuth 1.0a, no X developer account. Just a Bearer key and JSON.
7-day free trial • No credit card required
One field turns any tweet into a scheduled one
To schedule a tweet from code, POST to OpenTweet's REST API athttps://opentweet.io/api/v1/postswith a scheduled_date in ISO 8601 format. That is the whole trick. OpenTweet holds the post and publishes it at that time, so there is no cron job, background worker, or queue for you to babysit.
Doing this against the raw X API means an approved developer account, OAuth 1.0a request signing, and then building your own scheduler on top. OpenTweet replaces all of it with a single ot_ key and plain JSON. The same endpoint works from curl, Node, Python, or anything that can send an HTTP request.
# Schedule a tweet for 9:00 UTC on July 10, 2026
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{"text":"Scheduled from code.","scheduled_date":"2026-07-10T09:00:00Z"}'Step-by-Step: Schedule Tweets from Code
Get an ot_ API key
Sign up for OpenTweet, connect your X account, and open the developer page at opentweet.io/developer. Generate an API key. It starts with ot_ and is the only credential you need. Store it in an environment variable, never in your source.
# Store your key as an environment variable
export OPENTWEET_API_KEY="ot_your_key"
# Sanity check the key with GET /me
curl https://opentweet.io/api/v1/me \
-H "Authorization: Bearer $OPENTWEET_API_KEY"Schedule one tweet with a scheduled_date
POST to /api/v1/posts with a JSON body. Set scheduled_date to an ISO 8601 timestamp. Do not send publish_now in the same request. The two fields are mutually exclusive. Here is the same call in curl, Node, and Python.
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer $OPENTWEET_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Scheduled from code.",
"scheduled_date": "2026-07-10T09:00:00Z"
}'const res = await fetch("https://opentweet.io/api/v1/posts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENTWEET_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Scheduled from code.",
scheduled_date: "2026-07-10T09:00:00Z",
}),
});
console.log(res.status); // 201 = created
console.log(await res.json());import os, requests
res = requests.post(
"https://opentweet.io/api/v1/posts",
headers={"Authorization": f"Bearer {os.environ['OPENTWEET_API_KEY']}"},
json={
"text": "Scheduled from code.",
"scheduled_date": "2026-07-10T09:00:00Z",
},
)
print(res.status_code) # 201 = created
print(res.json())publish_now and scheduled_date are exclusive
Send one or the other, never both in the same request. Omit both and the post is saved as a draft you can schedule later. To send it live immediately, use{"text":"...","publish_now":true}.Batch schedule up to 50 at once
Building a whole week or month at a time? Create the posts as drafts first, collect their ids, then hand them to /posts/batch-schedule. Send a schedules array of up to 50 objects, each with a post_id and a scheduled_date. One request instead of fifty.
const BASE = "https://opentweet.io/api/v1";
const HEADERS = {
Authorization: `Bearer ${process.env.OPENTWEET_API_KEY}`,
"Content-Type": "application/json",
};
const texts = [
"Monday: ship something today.",
"Tuesday: write the test first.",
"Wednesday: simplicity beats cleverness.",
];
// Create drafts (omit publish_now and scheduled_date) and collect ids
const ids = [];
for (const text of texts) {
const res = await fetch(`${BASE}/posts`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ text }), // no date = draft
});
const draft = await res.json();
ids.push(draft.id);
}
// Schedule them at 9:00 UTC on consecutive days (max 50 per call)
const start = new Date("2026-07-10T09:00:00Z");
const schedules = ids.map((post_id, i) => ({
post_id,
scheduled_date: new Date(start.getTime() + i * 86400000).toISOString(),
}));
const res = await fetch(`${BASE}/posts/batch-schedule`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ schedules }),
});
console.log(`Scheduled ${schedules.length} tweets`, res.status);Just want a UI for this?
Prefer to bulk schedule by pasting or uploading in the dashboard instead of writing code? See bulk schedule tweets. This page is the code path. That one is the point and click path.Verify the queue with GET /posts
Confirm everything landed. Call GET /posts?status=scheduled to list every post waiting to go out. Great for a health check, a dashboard, or a nightly report of the week ahead.
curl "https://opentweet.io/api/v1/posts?status=scheduled" \
-H "Authorization: Bearer $OPENTWEET_API_KEY"import os, requests
res = requests.get(
"https://opentweet.io/api/v1/posts",
headers={"Authorization": f"Bearer {os.environ['OPENTWEET_API_KEY']}"},
params={"status": "scheduled"},
)
for post in res.json().get("posts", []):
print(post["scheduled_date"], "->", post["text"][:60])Errors and rate limits
Every error is JSON with a human message and a machine code. Fix permanent errors, back off on the temporary ones.
{ "error": "human message", "code": "machine_code" }The request is malformed. Fix the body and do not retry.
Check the Authorization header carries your ot_ key. Do not retry until fixed.
Your plan or account cannot perform this action. Do not retry.
Too Many Requests. Back off and retry with exponential backoff.
A server error. Retry with exponential backoff.
Retry the right things
Do not retry permanent 4xx errors. Fix the request instead. Only retry 429 and 5xx, and space those out with exponential backoff. Rate limits are per minute: Pro 60, Advanced 300, Agency 600. Daily post limits are Pro 20, Advanced 100, Agency 300. Going over returns HTTP 429.Pro Tips
Always send UTC
Use a trailing Z on scheduled_date, like 2026-07-10T09:00:00Z. Compute the timestamp in UTC so a server in another timezone schedules exactly when you mean.
Verify the key at startup
Call GET /me once when your service boots. A 200 confirms the key is live before you try to schedule, so you fail fast on a bad or revoked key.
Target a specific account
On multi account plans, pass x_account_id in the body to schedule to a chosen X account instead of your primary one.
Schedule threads too
Add is_thread true and thread_tweets to a scheduled post. The same scheduled_date applies to the whole thread.
Common Mistakes to Avoid
Sending publish_now and scheduled_date together
They are mutually exclusive. Send one or the other. Include both and the request is rejected with a 400. Omit both only when you want a draft.
Using a local time string
scheduled_date must be ISO 8601. Prefer UTC with a trailing Z so the tweet fires at the moment you intend regardless of server timezone.
Using api.opentweet.io
That host does not resolve. The REST base is https://opentweet.io/api/v1. Point every request there and keep your ot_ key in the Authorization header.
Frequently Asked Questions
How do I schedule tweets programmatically?
Send a POST request to https://opentweet.io/api/v1/posts with your ot_ key as a Bearer token and a JSON body like {"text":"Hello","scheduled_date":"2026-07-10T09:00:00Z"}. The scheduled_date is an ISO 8601 timestamp. OpenTweet publishes the tweet at that time, so you never run your own cron or background worker.
What date format does scheduled_date use?
ISO 8601, for example 2026-07-10T09:00:00Z. The trailing Z means UTC. publish_now and scheduled_date are mutually exclusive, so send one or the other, never both. Omit both to save the post as a draft you can schedule later.
Can I schedule many tweets in one request?
Yes. POST to https://opentweet.io/api/v1/posts/batch-schedule with a schedules array of up to 50 objects, each carrying a post_id and a scheduled_date. Create the posts as drafts first to get their ids, then hand them all to batch-schedule in one call.
Do I need the Twitter API or OAuth to schedule tweets from code?
No. The X API needs an approved developer account and OAuth 1.0a request signing. OpenTweet uses a single ot_ Bearer key and plain JSON, so there is no OAuth flow, no signature, and no developer portal application to build.
How do I confirm a tweet is scheduled?
Call GET https://opentweet.io/api/v1/posts?status=scheduled with your ot_ key. It returns every post waiting to publish, which is handy for a dashboard, a health check, or a nightly report of the week ahead.
Keep exploring
OpenTweet vs the X API
Post, schedule, and automate X without a developer account or the $200/mo minimum.
Post to X without an API
The clean, account-safe way to post to X from your code or an AI agent.
Twitter MCP Server
Give Claude, Cursor, and OpenClaw the ability to post to X. 36 tools included.
Developer docs
Quickstart, API keys, MCP setup, and the REST reference for posting to X from code or an agent.
XMCP vs OpenTweet
X's official MCP server bills per API call and cannot schedule. Compare it with the flat-fee hosted MCP.
MCP for AI agents
Connect your AI client to X in under two minutes, no X developer account.
Developer API and keys
REST endpoints, one bearer key, and usage tracking. Build on OpenTweet.
OpenTweet for AI agents
The posting layer for autonomous agents and automations that live on X.
Build an AI Twitter persona
Give your AI agent its own X account. Setup, cadence, and the rules that keep it safe.
Start scheduling tweets from code today
Get your ot_ key in 2 minutes. Schedule your first tweet with one POST.