API Tutorial

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.

the-whole-idea.sh
# 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

1

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.

bash
# 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"
2

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.

schedule.sh
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"
  }'
schedule.mjs
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());
schedule.py
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}.
3

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.

batch-schedule.mjs
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.
4

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.

bash
curl "https://opentweet.io/api/v1/posts?status=scheduled" \
  -H "Authorization: Bearer $OPENTWEET_API_KEY"
verify.py
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-shape.json
{ "error": "human message", "code": "machine_code" }
400validation_failed / invalid_json

The request is malformed. Fix the body and do not retry.

401missing or invalid key

Check the Authorization header carries your ot_ key. Do not retry until fixed.

403subscription_required

Your plan or account cannot perform this action. Do not retry.

429rate limit

Too Many Requests. Back off and retry with exponential backoff.

500internal_error

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.

Start scheduling tweets from code today

Get your ot_ key in 2 minutes. Schedule your first tweet with one POST.