To integrate the X API (formerly the Twitter API) into a Django app in 2026, register an app in the X developer portal (developer.x.com), run the OAuth 2.0 Authorization Code flow with PKCE to obtain a user access token, then call X API v2 at https://api.twitter.com/2 with an Authorization: Bearer <token> header — for example GET /2/users/me to read the signed-in account and POST /2/tweets to publish a post. The Tweepy library (tweepy.Client) wraps v2 and the PKCE handler, so you rarely touch the raw endpoints by hand.
This is a 2026 rewrite of our original Twython tutorial. Twitter is now X, the old API v1.1 is largely retired, OAuth 1.0a has given way to OAuth 2.0 + PKCE, and meaningful read access now requires a paid API tier (the Free tier is essentially write-only). We use this exact pattern when we ship Django applications for clients.
Key takeaways
- Twitter is X, and v1.1 is gone. Build on X API v2 (
api.twitter.com/2); legacy v1.1 endpoints and scraping no longer work. - OAuth 2.0 with PKCE is the user-login flow: authorize, handle the callback, then exchange the
codeplus acode_verifierfor an access token. - Access is now paid and tiered. Free is write-only with tiny limits; Basic, Pro, and Enterprise unlock real read and timeline access (names only here — confirm current details in the portal).
- Bearer auth everywhere. Send
Authorization: Bearer <token>; an App-Only Bearer token covers read-only, app-context calls. - Use Tweepy.
tweepy.Clientandtweepy.OAuth2UserHandlersupport v2; the old Twython and python-twitter libraries are abandoned. - Least-privilege scopes. Request only what you need:
tweet.read,tweet.write,users.read, andoffline.access(for refresh tokens).
Why does the old Twython tutorial no longer work?
If you followed a pre-2023 guide, almost every assumption in it is now wrong. Twitter rebranded to X and retired the bulk of API v1.1 along with free, open access. Here is what actually changed:
- New API surface. The current API is X API v2, served from
https://api.twitter.com/2and documented atdeveloper.x.com. Endpoints, payloads, and field selection all differ from v1.1. - New auth. User actions use OAuth 2.0 Authorization Code with PKCE instead of OAuth 1.0a. App-only, read-only calls use an OAuth 2.0 App-Only Bearer token.
- Paid tiers. Access now requires a plan. The Free tier is effectively write-only (post on behalf of the authenticated user) with a small monthly cap; Basic, Pro, and Enterprise unlock progressively more read, search, and volume.
- Abandoned libraries.
Twythonandpython-twittertargeted v1.1 and are no longer maintained. The actively maintained choice is Tweepy, which added first-class v2 support. - No scraping. Unofficial scraping and old endpoints are blocked or throttled aggressively — use the official v2 API.
How does X API v2 compare to the old v1.1 API?
| Aspect | API v1.1 (legacy) | API v2 (current) |
|---|---|---|
| Status | Largely retired / deprecated | Actively developed |
| Base URL | api.twitter.com/1.1 |
api.twitter.com/2 |
| User auth | OAuth 1.0a | OAuth 2.0 Auth Code + PKCE |
| App auth | OAuth 1.0a app-only | OAuth 2.0 App-Only Bearer |
| Post a tweet | POST statuses/update |
POST /2/tweets |
| Read profile | GET account/verify_credentials |
GET /2/users/me |
| Python library | Twython, python-twitter | Tweepy (tweepy.Client) |
| Post length | 140 characters (original) | 280+ characters |
Which X API access tier do you need?
Plans change often, so we deliberately list names, not numbers — confirm current limits and costs in the developer portal. The key planning fact: if your Django feature needs to read timelines, run search, or fetch other users' data, the Free tier will not be enough.
| Tier | Read access | Posting | Typical use |
|---|---|---|---|
| Free | Essentially none | Small monthly write allowance | Testing, write-only bots |
| Basic | Limited | Higher | Hobby and small apps |
| Pro | Broader, includes search | Higher | Production apps and startups |
| Enterprise | Full, high-volume / firehose | Highest | Large-scale data and analytics |
Decide your tier before you build: it dictates which v2 endpoints and scopes are actually usable for your app.
How do you register an app in the X developer portal?
- Sign in at developer.x.com and create (or open) a Project, then add an App inside it.
- Open the app's User authentication settings, enable OAuth 2.0, set the Type of App to a Web App / confidential client (a Django server can keep a secret), and add your Callback / Redirect URI — for local work, something like
http://127.0.0.1:8000/auth/x/callback/. - Copy the Client ID and Client Secret and store them as environment variables — never commit them to source control.
- Choose the scopes your feature needs, e.g.
tweet.read,tweet.write,users.read, andoffline.access(the last one is required to receive a refresh token).
How do you set up the OAuth 2.0 PKCE flow in Django?
Install the libraries and load your credentials from the environment. We use requests for the explicit flow below so each step is visible, then show the shorter Tweepy path afterwards. You can also use requests-oauthlib or Authlib if you prefer a full OAuth client.
pip install requests tweepy# settings.py
import os
X_CLIENT_ID = os.environ["X_CLIENT_ID"]
X_CLIENT_SECRET = os.environ["X_CLIENT_SECRET"]
X_REDIRECT_URI = "http://127.0.0.1:8000/auth/x/callback/"
# Scopes: offline.access is required for a refresh token.
X_SCOPES = ["tweet.read", "tweet.write", "users.read", "offline.access"]Step 1 — Send the user to X to authorize
PKCE replaces the OAuth 1.0a request-token dance. You generate a high-entropy code verifier, derive an S256 code challenge, stash the verifier and a CSRF state in the session, then redirect the user to X's authorize URL.
# views.py
import base64, hashlib, secrets, urllib.parse
from django.conf import settings
from django.shortcuts import redirect
AUTHORIZE_URL = "https://x.com/i/oauth2/authorize"
def x_login(request):
# PKCE: high-entropy verifier -> SHA-256 S256 challenge
verifier = secrets.token_urlsafe(64)
challenge = (
base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest())
.rstrip(b"=")
.decode()
)
state = secrets.token_urlsafe(32)
request.session["x_pkce_verifier"] = verifier
request.session["x_oauth_state"] = state
params = {
"response_type": "code",
"client_id": settings.X_CLIENT_ID,
"redirect_uri": settings.X_REDIRECT_URI,
"scope": " ".join(settings.X_SCOPES),
"state": state,
"code_challenge": challenge,
"code_challenge_method": "S256",
}
return redirect(f"{AUTHORIZE_URL}?{urllib.parse.urlencode(params)}")
# urls.py
# path("auth/x/login/", x_login, name="x_login"),
# path("auth/x/callback/", x_callback, name="x_callback"),
# path("auth/x/me/", x_profile, name="x_profile"),Step 2 — Handle the callback and exchange the code
X redirects back to your callback URL with code and state. Verify the state, then POST the code together with the stored code_verifier to the token endpoint. A confidential client (your Django server) authenticates with HTTP Basic using the Client ID and Client Secret.
# views.py
import requests
from django.conf import settings
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect
TOKEN_URL = "https://api.twitter.com/2/oauth2/token"
def x_callback(request):
if request.GET.get("state") != request.session.get("x_oauth_state"):
return HttpResponseBadRequest("Invalid OAuth state")
data = {
"grant_type": "authorization_code",
"code": request.GET.get("code"),
"redirect_uri": settings.X_REDIRECT_URI,
"code_verifier": request.session["x_pkce_verifier"],
"client_id": settings.X_CLIENT_ID,
}
resp = requests.post(
TOKEN_URL,
data=data,
auth=(settings.X_CLIENT_ID, settings.X_CLIENT_SECRET), # confidential client
timeout=10,
)
resp.raise_for_status()
tokens = resp.json()
request.session["x_access_token"] = tokens["access_token"]
request.session["x_refresh_token"] = tokens.get("refresh_token")
return redirect("x_profile")Step 3 — Read the signed-in account with GET /2/users/me
With a user access token you can call v2 endpoints. The v2 API uses field selection, so ask for exactly the fields you need via the user.fields query parameter.
# views.py
import requests
from django.http import JsonResponse
def x_profile(request):
token = request.session["x_access_token"]
r = requests.get(
"https://api.twitter.com/2/users/me",
headers={"Authorization": f"Bearer {token}"},
params={"user.fields": "username,name,profile_image_url,verified"},
timeout=10,
)
r.raise_for_status()
return JsonResponse(r.json())
# -> {"data": {"id": "...", "name": "...", "username": "...", ...}}Step 4 — Post on behalf of the user with POST /2/tweets
Publishing replaces the old statuses/update. Send a JSON body to POST /2/tweets; this needs the tweet.write scope and a tier that permits posting (even the Free tier allows a small write allowance).
# views.py
import requests
from django.http import JsonResponse
def post_tweet(request):
token = request.session["x_access_token"]
r = requests.post(
"https://api.twitter.com/2/tweets",
headers={"Authorization": f"Bearer {token}"},
json={"text": "Posted from a Django app using the X API v2"},
timeout=10,
)
r.raise_for_status()
return JsonResponse(r.json(), status=201)
# -> {"data": {"id": "...", "text": "..."}}Can Tweepy handle all of this for you?
Yes — and it is the recommended approach. Tweepy ships an OAuth2UserHandler for the PKCE flow and a tweepy.Client for v2 calls, so you skip the manual challenge/exchange code above.
# views.py
import tweepy
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import redirect
handler = tweepy.OAuth2UserHandler(
client_id=settings.X_CLIENT_ID,
client_secret=settings.X_CLIENT_SECRET, # confidential client
redirect_uri=settings.X_REDIRECT_URI,
scope=settings.X_SCOPES,
)
def x_login(request):
return redirect(handler.get_authorization_url())
def x_callback(request):
# Exchange the full callback URL for a token, then call v2.
token = handler.fetch_token(request.build_absolute_uri())
client = tweepy.Client(token["access_token"])
client.create_tweet(text="Posted from Django via Tweepy + X API v2")
me = client.get_me(user_fields=["username", "name"])
return JsonResponse({"username": me.data.username})What about read-only, app-context calls?
When you do not need a logged-in user — for example a backend job that reads public data your tier permits — use an OAuth 2.0 App-Only Bearer token from the portal. There is no user context, so you cannot post as a user, but reads are simpler.
# jobs.py
import os
import tweepy
client = tweepy.Client(bearer_token=os.environ["X_BEARER_TOKEN"])
# Read recent posts from a user id (subject to your access tier's limits).
resp = client.get_users_tweets(id="2244994945", max_results=5)
for tweet in resp.data or []:
print(tweet.id, tweet.text)Where to go next
The same OAuth 2.0 pattern powers most social integrations, only the endpoints and scopes change. See our companion guides on integrating the LinkedIn API in Python Django and integrating the GitHub API in Python Django. If you would rather have us build and maintain the integration, that is what our Django development services team does every week.
Frequently Asked Questions
Is the Twitter API v1.1 still available?
The bulk of API v1.1 has been retired, and the endpoints that linger are deprecated and unreliable for new work. Build on X API v2 at https://api.twitter.com/2 instead. v2 uses different routes (for example POST /2/tweets rather than statuses/update), OAuth 2.0 instead of OAuth 1.0a, and explicit field selection. Porting an old v1.1 integration means rewriting both the auth and the endpoint calls.
Do I need a paid plan to use the X API?
Yes. Free, open access ended. The Free tier is essentially write-only — it lets the authenticated user post with a small monthly allowance and almost no read access. To read timelines, run search, or pull other users' data you need a higher tier: Basic, Pro, or Enterprise. Confirm the current limits in the developer portal before you design your feature, because the tier you choose decides which endpoints and scopes are usable.
Which OAuth flow should I use for user login?
Use OAuth 2.0 Authorization Code with PKCE for any feature that acts on behalf of a logged-in user, such as posting or reading their account. You generate a code verifier and S256 challenge, redirect the user to X to authorize, then exchange the returned code plus the verifier for an access token. For backend, read-only calls that need no user, use an OAuth 2.0 App-Only Bearer token instead. OAuth 1.0a from the old tutorials is legacy.
Should I use Tweepy or raw requests?
Tweepy is the recommended library: tweepy.OAuth2UserHandler runs the PKCE flow and tweepy.Client wraps the v2 endpoints, including create_tweet and get_me. Reach for plain requests (or requests-oauthlib / Authlib) only when you want full control over the HTTP calls or you are debugging the raw flow. Both talk to the same v2 API, so you can mix them in one project.
Can I still use Twython or python-twitter?
No. Both libraries targeted API v1.1 and are no longer maintained, so they break against the current platform. Migrate to Tweepy, which has first-class v2 support, or call the v2 endpoints directly with requests. If you are upgrading the old Twython code from this article, replace Twython and its OAuth 1.0a token handling with tweepy.Client and the OAuth 2.0 PKCE flow shown above.
How do I post a tweet from Django?
Get a user access token through the OAuth 2.0 PKCE flow, then send a JSON body to POST /2/tweets with an Authorization: Bearer <token> header, or call client.create_tweet(text=...) in Tweepy. The app needs the tweet.write scope, and your account must be on a tier that allows posting — the Free tier permits a small monthly write allowance, which is enough for testing and simple bots.