Prerequisites
OAuth 2.0 enabled on your app
Your stored OAuth 1.0a credentials
A refresh-token loop
The exchange request
Make one HTTPS call per user to the same token endpoint you use for OAuth 2.0 refreshes:application/x-www-form-urlencoded with these parameters:
Authorization: Basic header containing the base64-encoded <oauth2_client_id>:<oauth2_client_secret>. Public clients omit the header and include client_id=<oauth2_client_id> in the body.
Success response (HTTP 200)
refresh_token (and current access_token) against the user, then call the API with Authorization: Bearer <access_token> exactly as for any OAuth 2.0 user.
What permissions do the new tokens get?
The OAuth 2.0 scopes are derived from what each user originally authorized under OAuth 1.0a, never more:bookmark.read or space.read) are not granted. To gain those, send the user through the normal OAuth 2.0 authorization flow. See the scopes reference for what each scope allows.
Migration walkthrough
For each stored OAuth 1.0a token:Send the exchange request
POST /2/oauth2/token with the exchange parameters above.On 200
access_token and refresh_token for the user and mark the user migrated.On 400 invalid_grant
On 429 rate_limited
On 5xx
Switch the user's traffic
Authorization: Bearer and your standard OAuth 2.0 refresh loop.Re-running and retries (rotation)
Exchanging the same OAuth 1.0a token again always works and returns a new OAuth 2.0 token pair, and invalidates the pair previously issued for that user and app. This makes migration scripts safely re-runnable, but it means you should always persist the most recent pair.Rate limits
The exchange is limited to 10,000 requests per 15 minutes per app per source IP address. A single worker migrating sequentially will rarely hit this; parallel workers should implement standard backoff on HTTP 429. At the full budget, one worker IP migrates roughly one million users per day.Errors
invalid_grant is deliberately generic. The response does not distinguish why a token was rejected.FAQ
Will my users notice anything?
Will my users notice anything?
Does the exchange log users out or break my OAuth 1.0a integration?
Does the exchange log users out or break my OAuth 1.0a integration?
What if I lose a refresh token, or it expires after 6 months?
What if I lose a refresh token, or it expires after 6 months?
Do exchanged tokens behave differently from normal OAuth 2.0 tokens?
Do exchanged tokens behave differently from normal OAuth 2.0 tokens?
What about the Ads API?
What about the Ads API?
ads.read and ads.write where the user’s original authorization included ads permissions, and these work on X API v2 ads endpoints. The legacy standalone Ads API (ads-api.x.com) continues to use OAuth 1.0a. Keep your OAuth 1.0a integration for it until further notice.Which users should I migrate?
Which users should I migrate?
invalid_grant users as churn to re-acquire through the normal login flow.