How-To Guide

How to auto-post to X from your app

Add share-to-X or automatic posting to your app or SaaS with a single server-side request. No per-user X OAuth flow to build, no X developer account, no token refresh logic. One POST and you are live.

Why not just call the X API directly?

Posting to X yourself means an X developer account, an app review, per-user OAuth, token storage, token refresh, and rate-limit handling. That is real work before your app can send a single tweet, and it is work you have to maintain forever.

OpenTweet collapses all of that into one bearer key and one endpoint. You connect an X account once in the dashboard, then your backend posts with a plain HTTP request. The X connection, retries, scheduling, and media hosting are handled for you, so shipping share-to-X becomes an afternoon, not a sprint.

Step by step

1

Get an API key

Create an OpenTweet account, connect your X account once, then generate an API key at opentweet.io/developer. Keys are prefixed with ot_ and carry a single bearer token you send on every request. This is the only credential your app needs. There is no per-user X OAuth flow to build and no X developer account to apply for.

Verify the key works with a quick call to the /me endpoint.

bash
curl https://opentweet.io/api/v1/me \
  -H "Authorization: Bearer ot_your_key"
2

Make one server-side POST to /api/v1/posts

Auto-posting is a single HTTP request. Send a POST to https://opentweet.io/api/v1/posts with your text and publish_now: true. OpenTweet handles the X connection, rate limits, and retries for you. Here is the same call in Node and Python.

node.js
// server-side only (Node 18+, native fetch)
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: "We just shipped v2. Auto-posted from our app.",
    publish_now: true,
  }),
});

const post = await res.json();
console.log(post.id, post.status);
python
# server-side only (Python 3, requests)
import os, requests

res = requests.post(
    "https://opentweet.io/api/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['OPENTWEET_API_KEY']}"},
    json={
        "text": "We just shipped v2. Auto-posted from our app.",
        "publish_now": True,
    },
)

post = res.json()
print(post["id"], post["status"])

Base URL

The REST base is https://opentweet.io/api/v1. There is no api.opentweet.io subdomain, so point your client at the main domain.
3

Schedule for later or publish now

The same endpoint schedules posts too. Pass scheduled_date with an ISO 8601 timestamp instead of publish_now. The two fields are mutually exclusive. Omit both and OpenTweet saves the post as a draft, which is handy when you want a human to approve app-generated content before it goes live.

json
// publish right now
{ "text": "Live now.", "publish_now": true }

// or schedule for later (ISO 8601, UTC)
{ "text": "Going live at 9am.", "scheduled_date": "2026-07-10T09:00:00Z" }

// omit both to save a draft you can review later
{ "text": "Draft for the team to approve." }

Need to queue a lot at once? Save drafts, then call POST /posts/batch-schedule with up to 50 { post_id, scheduled_date } pairs.

4

Attach media with /upload then media_urls

To post an image or video, upload the file first with POST /upload, which returns a hosted URL. Then reference that URL in the media_urls array on your post. This keeps binary uploads separate from the JSON post body, so your app can reuse the same image across several posts.

node.js
// 1. upload the image, get back a URL
const form = new FormData();
form.append("file", imageBlob, "launch.png");

const up = await fetch("https://opentweet.io/api/v1/upload", {
  method: "POST",
  headers: { "Authorization": `Bearer ${process.env.OPENTWEET_API_KEY}` },
  body: form,
});
const { url } = await up.json();

// 2. attach it to the post via media_urls
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: "Screenshot of the new dashboard.",
    media_urls: [url],
    publish_now: true,
  }),
});
5

Keep the key server-side

An OpenTweet API key can post to your X account, so treat it like a password. Store it in a server-side environment variable and only call the OpenTweet API from your backend. Never ship the key to a browser, a mobile bundle, or a public repo. If a key is ever exposed, rotate it instantly from the developer dashboard, which invalidates the old one.

Do not call the API from the client

Frontend code is readable by anyone. Proxy every OpenTweet request through your own backend so the ot_ key stays secret.

What auto-posting through OpenTweet gives you

No per-user X OAuth flow to build or maintain
No X developer account or app review
One ot_ bearer key for every request
Publish now, schedule, or save as a draft
Threads via is_thread and thread_tweets
Batch schedule up to 50 posts in one call
Media uploads through /upload plus media_urls
Works from Node, Python, or any REST client

Frequently asked questions

Do I need an X developer account to auto-post from my app?

No. OpenTweet handles the X connection for you. You connect your X account once in the OpenTweet dashboard, then your app posts through the OpenTweet REST API with a single ot_ key. There is no X developer account, no app review, and no elevated access request.

Do I have to build a per-user X OAuth flow?

No. That is the main thing OpenTweet removes. You do not implement the X OAuth handshake, token storage, or token refresh. Your app makes one server-side POST to https://opentweet.io/api/v1/posts with a bearer key and OpenTweet publishes to the connected X account.

What is the difference between publish_now and scheduled_date?

publish_now: true posts immediately. scheduled_date takes an ISO 8601 timestamp and queues the post for that time. The two are mutually exclusive, so send one or the other. If you omit both, the post is saved as a draft you can review or schedule later.

How do I post a thread instead of a single tweet?

Send is_thread: true with a thread_tweets array of strings, for example {"is_thread": true, "thread_tweets": ["first", "second"], "thread_media": [[], []]}. OpenTweet chains the tweets into a proper reply thread on X.

Can I schedule many posts at once from my app?

Yes. Save posts as drafts, then call POST /posts/batch-schedule with a schedules array of up to 50 {post_id, scheduled_date} objects. Each entry gets its own publish time.

What is the base URL for the API?

https://opentweet.io/api/v1 . The api.opentweet.io subdomain does not resolve, so always call the main opentweet.io domain. Send Authorization: Bearer ot_your_key on every request.

Which agents and clients can post through OpenTweet?

Any REST client works. There is also an MCP server at https://mcp.opentweet.io/mcp and a CLI (npm install -g @opentweet/cli), so Claude Code, Codex, Cursor, Windsurf, ChatGPT, OpenClaw, Hermes, and any MCP or REST client can auto-post to X.

Ship auto-posting to X today

Grab a key, make one POST, and your app is publishing to X. No OAuth flow, no developer account.

Get your API key