Post to X from
LangChain, CrewAI & OpenAI Agents SDK
Give any AI agent a tweet tool in about a dozen lines. Copy-paste definitions for the three biggest frameworks — each posts to X through OpenTweet, so you never touch X OAuth, token refresh, or rate limits.
7-day free trial • ot_ API key included • No X developer app
Why route your agent through OpenTweet?
Wiring an AI agent directly to the X (Twitter) API is the kind of task that looks like an afternoon and turns into a week. You have to register a developer app, implement the OAuth flow, persist and refresh access tokens, handle per-endpoint rate limits and 429s, and rebuild media upload — then babysit all of it every time X changes a policy.
OpenTweet collapses that to a single HTTP call. You connect your X account once, generate an ot_ API key, and your agent posts with a plain POST /api/v1/posts. The token refresh, rate limiting, scheduling, multi-account routing, and evergreen queue all live on OpenTweet's side — so your tool stays a dozen lines that don't break.
The patterns below are copy-paste for LangChain, CrewAI, and the OpenAI Agents SDK. Building a custom loop or using another framework? The same request works anywhere — see how to tweet from any AI agent and the AI agents overview.
What OpenTweet handles so your agent doesn't
Everything below is plumbing you'd otherwise write and maintain against the raw X API.
No X OAuth or developer app
You connect X to OpenTweet once. Your agent never handles X developer credentials, the 3-legged OAuth dance, or app review.
No token refresh
OpenTweet keeps your X access token alive. Self-hosting against the X API means writing refresh logic that silently breaks at the worst time.
No rate-limit plumbing
OpenTweet queues and posts on your behalf. You skip the 429-handling, backoff, and per-endpoint limit bookkeeping the raw X API forces on you.
Multi-account out of the box
Pass x_account_id to target any connected account. No re-authing or juggling separate credential sets per handle.
Scheduling + evergreen built in
Send scheduled_date to queue a post, or drop tweets into the evergreen queue — capabilities the bare X API does not provide at all.
Draft-first safety
Default the tool to drafts so a human approves each post. Flip to autonomous publishing only when you trust the agent.
Get your OpenTweet API key
Sign up and connect your X (Twitter) account — this is the only place X credentials live. Open the API section in the sidebar and click Generate API Key. Your key starts with ot_. Copy it now; you won't see it again.
Store it as an environment variable so the snippets below can read it:
export OPENTWEET_API_KEY="ot_your_key_here"
All paid plans include API access; the 7-day free trial unlocks it for testing. Full reference: /docs/api.
Add the tweet tool to your framework
Pick your framework. Each tool calls POST /api/v1/posts with your ot_ key and defaults to drafts unless you ask it to publish or schedule. All three are interchangeable — the OpenTweet half is identical; only the framework wrapper differs.
LangChain
Define the tweet tool with the @tool decorator, then hand it to a ReAct-style agent. The LLM decides when to call it.
import os, requests
from langchain_core.tools import tool
OPENTWEET = "https://opentweet.io/api/v1/posts"
HEADERS = {"Authorization": f"Bearer {os.environ['OPENTWEET_API_KEY']}"}
@tool
def post_to_x(text: str, scheduled_date: str | None = None,
publish_now: bool = False) -> str:
"""Create, schedule, or publish a tweet on X (Twitter) via OpenTweet.
Args:
text: The tweet body.
scheduled_date: Optional ISO 8601 time, e.g. "2026-06-01T09:00:00Z".
publish_now: If True, post immediately. If both are omitted the
tweet is saved as a draft for human review (safest default).
"""
payload: dict = {"text": text}
if scheduled_date:
payload["scheduled_date"] = scheduled_date
elif publish_now:
payload["publish_now"] = True
r = requests.post(OPENTWEET, headers=HEADERS, json=payload, timeout=30)
r.raise_for_status()
data = r.json()
return data.get("url") or "Tweet created (draft)."Then wire it into an agent:
from langchain.chat_models import init_chat_model
from langgraph.prebuilt import create_react_agent
llm = init_chat_model("anthropic:claude-opus-4-6") # any LangChain model
agent = create_react_agent(llm, tools=[post_to_x])
agent.invoke({"messages": [
{"role": "user", "content": "Schedule a tweet for tomorrow 9am UTC announcing v2.0"}
]})CrewAI
Wrap the call in a CrewAI @tool and attach it to a Social Media Manager agent. Multi-agent crews can research, write, and post in one flow.
import os, requests
from crewai.tools import tool
@tool("Post to X")
def post_to_x(text: str) -> str:
"""Publish a tweet on X (Twitter) through OpenTweet. Input is the tweet text."""
r = requests.post(
"https://opentweet.io/api/v1/posts",
headers={"Authorization": f"Bearer {os.environ['OPENTWEET_API_KEY']}"},
json={"text": text, "publish_now": True},
timeout=30,
)
r.raise_for_status()
return r.json().get("url", "Posted.")Then wire it into an agent:
from crewai import Agent, Task, Crew
social = Agent(
role="Social Media Manager",
goal="Grow the company's X audience with on-brand posts",
backstory="You write punchy, valuable tweets and never spam.",
tools=[post_to_x],
)
Crew(agents=[social], tasks=[
Task(description="Write and post a launch-day tweet for our new API.",
agent=social, expected_output="The live tweet URL")
]).kickoff()OpenAI Agents SDK
Use the @function_tool decorator and pass it to an Agent. The Runner executes the loop and calls your tool when posting is needed.
import os, requests
from agents import Agent, Runner, function_tool
@function_tool
def post_to_x(text: str) -> str:
"""Post a tweet to X (Twitter) via OpenTweet and return the tweet URL."""
r = requests.post(
"https://opentweet.io/api/v1/posts",
headers={"Authorization": f"Bearer {os.environ['OPENTWEET_API_KEY']}"},
json={"text": text, "publish_now": True},
timeout=30,
)
r.raise_for_status()
return r.json().get("url", "Posted.")
agent = Agent(
name="Social agent",
instructions="You manage the company's X account. Keep posts on-brand.",
tools=[post_to_x],
)
Runner.run_sync(agent, "Draft and post a tweet announcing our launch.")Threads, scheduling & multi-account
The same endpoint does more than fire single tweets. Add scheduled_date (ISO 8601) to queue a post for later, is_thread with thread_tweets to publish a full thread, or x_account_id to target a specific handle on multi-account plans. Your agent passes these as part of the same JSON body:
# Post a multi-tweet thread (works from any framework's tool)
payload = {
"text": "How we cut our infra bill 60% — a thread 🧵", # first tweet
"is_thread": True,
"thread_tweets": [
"How we cut our infra bill 60% — a thread 🧵",
"1/ We started by profiling what actually drove cost...",
"2/ Turns out 80% came from one chatty cron job.",
"3/ Lessons + the exact config we shipped 👇",
],
}
requests.post(OPENTWEET, headers=HEADERS, json=payload, timeout=30)See the full field list, batch scheduling, and analytics endpoints in the API docs. Prefer a native protocol for AI assistants like Claude or Cursor? Use the MCP server instead.
Run draft-first, then automate
Ship your agent with the tool defaulting to drafts (omit publish_now and scheduled_date). Every post lands in your OpenTweet dashboard for review. Watch a week of output, confirm the voice and accuracy, then let the agent publish or schedule on its own by flipping publish_now=True or passing a scheduled_date.
Pair it with voice learning so the tweets sound like you, and connect content connectors (RSS, GitHub, Stripe) to feed your agent fresh material automatically.
Pro Tips
Return the URL to the agent
Have the tool return the live tweet URL (or "draft created"). Feeding the result back lets the agent confirm success, retry on failure, or chain a follow-up reply.
Keep the docstring tight
The tool docstring IS the prompt the LLM reads to decide when and how to call it. Spell out that scheduled_date is ISO 8601 and that omitting both flags means draft — accurate docstrings cut malformed calls.
Start every agent in draft mode
Ship with publish_now=False. Watch a week of drafts in the dashboard, confirm voice and accuracy, then let the agent publish or schedule autonomously.
Add voice learning
Enable voice matching in OpenTweet so agent-written tweets sound like you, not like generic AI. The model learns your vocabulary and cadence from past posts.
Common Mistakes to Avoid
Rebuilding the X API yourself
Wiring an agent straight to the X API means owning OAuth, token refresh, rate limits, and media upload. That is days of plumbing that breaks on every X policy change — the OpenTweet tool is a dozen lines.
Letting agents publish on day one
Autonomous posting without a review period is how off-brand or hallucinated tweets go live. Default to drafts until the output earns your trust.
Hardcoding the API key
Read OPENTWEET_API_KEY from an environment variable or secrets manager. Never commit ot_ keys or ship them in client-side code; rotate immediately if one leaks.
Ignoring the tool result
If the tool swallows errors, the agent thinks every post succeeded. Raise on non-2xx and return a clear message so the agent can react.
Frequently Asked Questions
How do I let a LangChain agent post to Twitter?
Define a LangChain @tool that sends a POST request to https://opentweet.io/api/v1/posts with your OpenTweet API key as a Bearer token, then add it to your agent's tools list. OpenTweet handles the X connection, so you don't write any X API, OAuth, or rate-limit code.
Does my agent need its own X (Twitter) API key?
No. You connect your X account to OpenTweet once. Your agent only needs an OpenTweet API key (ot_) — it never touches X developer credentials, OAuth tokens, or token refresh.
What's the difference between this and the MCP server?
The MCP server is for AI assistants like Claude and Cursor. This guide is for agent frameworks you build yourself — LangChain, CrewAI, and the OpenAI Agents SDK — where you register a custom tool function. Both call the same OpenTweet backend.
Can my agent schedule tweets or post threads, not just publish?
Yes. The same endpoint accepts scheduled_date (ISO 8601) to schedule, is_thread with thread_tweets to post a full thread, and x_account_id to target a specific account on multi-account plans.
Is it safe to let an autonomous agent post to my account?
Default the tool to creating drafts (omit publish_now and scheduled_date) so a human reviews each post in the OpenTweet dashboard. Switch to publish_now once you trust the output. You can rotate or revoke the API key any time.
Which frameworks are supported?
Any framework that can call an HTTP endpoint. This guide gives ready-made tools for LangChain, CrewAI, and the OpenAI Agents SDK; the same POST /api/v1/posts request works from AutoGPT, Letta, Pydantic AI, or your own loop.
Give your agent a tweet tool today
One ot_ key, one HTTP call. No X developer app, no OAuth, no rate-limit code.