---
title: "Configure X (Twitter)"
description: "Complete BYOK setup for X: create the developer app, configure OAuth 2.0 with PKCE, copy the correct client credentials and test media publishing."
section: "Platforms"
url: https://dravo.dev/docs/platforms/x
---
# Configure X (Twitter)

Dravo uses X API v2 with an OAuth 2.0 **user access token**. Authorization Code
with PKCE identifies the user, while `offline.access` provides the refresh token
needed for a persistent connection.

_Platform configuration verified against X's official documentation on June
24, 2026._

## Configuration summary

| Setting | Value |
| --- | --- |
| Dravo platform | `x` |
| Developer console | `https://console.x.com` |
| OAuth flow | OAuth 2.0 Authorization Code with PKCE |
| App type | Web App, Automated App or Bot |
| App permissions | Read and write |
| Callback | `https://api.dravo.dev/v1/oauth/x/callback` |
| Credentials | OAuth 2.0 Client ID and Client Secret |
| Required scopes | `tweet.write`, `tweet.read`, `users.read`, `offline.access`, `media.write` |
| Analytics scopes | No extra Dravo scope; post and account metrics use `tweet.read` and `users.read` with user context |
| Billing | X API pay-per-usage credits |

The callback is fixed for every Dravo customer. Do not replace `api.dravo.dev`
with your product domain.

## Step 1: Create an X developer account

### 1.1 Enroll in the Developer Console

**Navigation:** [X Developer Console](https://console.x.com) → **Sign in**.

1. Sign in with the X account that should own the developer organization.
2. Accept the Developer Agreement and platform policies.
3. Complete the requested use-case description and organization details.
4. Confirm that the Developer Console dashboard opens successfully.

Use an organizational account and retain a second administrator where X allows
it. Losing access to the owner account can block credential rotation.

### 1.2 Configure API credits

**Navigation:** `Developer Console → Billing` or **Credits/Usage** in the
current console navigation.

1. Add a payment method when required.
2. Purchase or enable enough API credits for create-post and media operations.
3. Set a spending limit and low-balance alert.
4. Remember that successful OAuth does not prove that paid API calls are funded.

X documents the self-serve API as pay-per-usage. Old Free/Basic/Pro tier
tutorials do not describe the current credit model.

## Step 2: Create the X app

### 2.1 Create an app

**Navigation:** `Developer Console dashboard → New App`.

1. Choose **New App**.
2. Enter a name that distinguishes development from production.
3. Describe the user-authorized social publishing use case.
4. Associate the app with the correct project/organization if the console asks.
5. Finish creation and open the app overview.

### 2.2 Identify the two credential families

**Navigation:** `App → Keys and tokens`.

X can show several unrelated credentials:

| Console label | Protocol | Used by Dravo |
| --- | --- | --- |
| API Key and API Key Secret | OAuth 1.0a | No |
| Bearer Token | App-only OAuth 2.0 | No |
| Access Token and Access Token Secret | OAuth 1.0a user context | No |
| OAuth 2.0 Client ID and Client Secret | OAuth 2.0 user context | **Yes** |

Do not copy anything yet. The OAuth 2.0 Client Secret is available only after
the confidential client type is configured in Step 3.

## Step 3: Configure OAuth 2.0 and the callback

### 3.1 Open User authentication settings

**Navigation:** `Developer Console → App → Settings → User authentication settings → Set up`.
If authentication already exists, the button is labelled **Edit**.

1. Enable **OAuth 2.0**.
2. Leave OAuth 1.0a disabled unless another integration explicitly needs it.
3. Continue to the app type and permissions section.

### 3.2 Select a confidential app type

Under **Type of App**, select **Web App, Automated App or Bot**. X classifies
this as a confidential client and issues a Client Secret. Do not select
**Native App** or **Single Page App**; those are public clients.

Dravo still performs PKCE even though it is a confidential server-side client.

### 3.3 Enable write access

Under **App permissions**, select **Read and write**.

1. Do not leave the app on **Read**.
2. Direct Message permission is not required by Dravo.
3. Save the setting before copying credentials.

This console choice makes write access possible. Dravo then asks the user for
the exact scopes below during OAuth:

| Scope | Why Dravo needs it |
| --- | --- |
| `tweet.write` | Create posts. |
| `tweet.read` | Read post resources required by user-context API operations. |
| `users.read` | Resolve `/2/users/me`, username and avatar. |
| `offline.access` | Receive a refresh token. |
| `media.write` | Upload images and videos. |

There is no free-form scope box in Dravo. Existing tokens must reconnect after
any permission or scope change.

### 3.4 Add the callback and website URL

In **User authentication settings**, locate **App info**:

1. Under **Callback URI / Redirect URL**, add:

   ```text
   https://api.dravo.dev/v1/oauth/x/callback
   ```

2. Under **Website URL**, enter the public website for your product. This is not
   the OAuth callback.
3. Add Terms of Service and Privacy Policy URLs when the console displays them.
4. Choose **Save** and reopen the page to confirm the callback remains present.

X requires an exact callback match. Do not add a wildcard, query string,
fragment or your frontend `return_url`.

## Step 4: Copy the OAuth 2.0 client credentials

### 4.1 Open Keys and tokens

**Navigation:** `Developer Console → App → Keys and tokens → OAuth 2.0 Client ID and Client Secret`.

1. Find the **Client ID** heading and copy its value.
2. Copy the **Client Secret** shown under it.
3. If the secret is no longer visible, choose **Regenerate** and copy the new
   value immediately.
4. Keep the Client ID and Secret together; they must belong to the same app.

### 4.2 Reject the wrong credentials

Before leaving X, verify that you did **not** copy:

1. API Key.
2. API Key Secret.
3. Bearer Token.
4. Access Token.
5. Access Token Secret.

Those values belong to different authentication modes and cause
`invalid_client` in Dravo's OAuth 2.0 callback.

## Step 5: Save the X app in Dravo

### 5.1 Save it from the dashboard

**Navigation:** `Dravo Dashboard → Connections → OAuth apps → Add OAuth app`.

1. Select **X (Twitter)**.
2. Enter a recognizable name such as `Production X App`.
3. Paste the OAuth 2.0 Client ID into **Client ID**.
4. Paste the OAuth 2.0 Client Secret into **Client Secret**.
5. Confirm the fixed callback and all five scopes displayed by Dravo.
6. Save the OAuth app.

### 5.2 Save it through the API

**Endpoint:** `POST /v1/oauth-apps`.

```bash
curl -X POST https://api.dravo.dev/v1/oauth-apps \
  -H "Authorization: Bearer $DRAVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "x",
    "name": "Production X App",
    "client_id": "OAUTH_2_CLIENT_ID",
    "client_secret": "OAUTH_2_CLIENT_SECRET",
    "allowed_return_urls": [
      "https://app.acme.com/settings/connections"
    ]
  }'
```

The response must show the callback from Step 3 and all five scopes. Dravo
encrypts the secret and never returns it.

## Step 6: Connect an X account

### 6.1 Connect from the dashboard

**Navigation:** `Dravo Dashboard → Connections → Connected accounts → Connect account`.

1. Select **X (Twitter)**.
2. Choose the OAuth app saved in Step 5.
3. Choose **Connect with OAuth**.
4. Sign in to the X account that should publish.
5. Review the requested permissions and authorize the app.
6. Allow X to return the browser to Dravo.

### 6.2 Connect from your backend

**Endpoint:** `GET /v1/oauth/x/start`.

```bash
curl --get "https://api.dravo.dev/v1/oauth/x/start" \
  -H "Authorization: Bearer $DRAVO_API_KEY" \
  --data-urlencode "oauth_app_id=YOUR_OAUTH_APP_ID" \
  --data-urlencode "return_url=https://app.acme.com/settings/connections"
```

Redirect the browser to `authorize_url`. Dravo creates and stores the PKCE
verifier automatically; your application never handles it.

## Step 7: Verify text and media publishing

### 7.1 Verify the identity and token

**Dashboard:** `Dravo Dashboard → Connections → Connected accounts`.
**API:** `GET /v1/accounts?platform=x`.

1. Confirm the username and X user id are correct.
2. Confirm `connection_type` is `oauth`.
3. Confirm `oauth_app_id` identifies the intended X app.
4. Confirm the stored scopes include all five values, especially
   `offline.access` and `media.write`.
5. Confirm token health is active.

### 7.2 Publish a text-only post

**Navigation:** `Dravo Dashboard → Playground`.

1. Select only the new X account.
2. Publish a short text post.
3. Confirm the post appears on the intended X profile.
4. If it fails with payment/access errors, inspect credits before changing OAuth.

### 7.3 Publish an image post

1. Use a small public image or upload/import one through Dravo media.
2. Publish it with a short caption.
3. Confirm the media upload and post creation both complete.

Text and media use different X API operations. A text success does not prove
that `media.write` or media credits are ready.

## Step 8: Prepare the app for production

### 8.1 Separate development and production apps

1. Keep a dedicated production X app and Dravo OAuth-app record.
2. Register the same fixed Dravo callback on that production app.
3. Repeat Steps 3–7 with the production Client ID and Secret.
4. Reconnect every account after moving it to a different X app.

### 8.2 Configure billing safeguards

**Navigation:** `Developer Console → Billing/Credits and Usage`.

1. Confirm create-post and media usage is funded.
2. Set a spending ceiling and low-credit alert.
3. Monitor response headers and the console for rate limits.
4. Test the expected daily volume before onboarding users.

### 8.3 Plan credential and consent changes

1. Rotating the Client Secret requires updating the Dravo OAuth app.
2. Adding a scope or changing Read/Write permissions requires reconnection.
3. Revoking the X app invalidates every account connected through it.
4. Keep a visible reconnect path in your product.

## Token and PKCE behavior

Dravo generates an S256 PKCE verifier/challenge for every authorization and
validates the returned state. There is no PKCE setting to paste into X.

X documents a default OAuth 2.0 access-token lifetime of two hours.
`offline.access` causes X to issue a refresh token; Dravo stores it and rotates
it when X returns a replacement. Without that scope the account quickly needs
reconnection.

Tokens already issued do not gain new scopes. Disconnect and reconnect after
adding `media.write`, enabling write access or changing the app configuration.

## Supported features

| Content | Current support | Notes |
| --- | --- | --- |
| Text | Yes | Created with X API v2. |
| Images | Yes | Up to 4 images (JPEG/PNG/WEBP/GIF) attached to one post. |
| Video | Yes | One MP4/MOV via chunked upload and processing-status polling. |
| Poll | Yes | 2 to 4 options with a duration (no media). |
| Reply / thread | Yes | Reply to an existing post via `reply_to`. |
| Account analytics | Yes | Uses `users.read` and X user `public_metrics` fields. |
| Post analytics | Yes | Uses `tweet.read`; public metrics are returned when available, while non-public/organic metrics require user context for owned posts and may be limited by API access/window. |
| Refresh | Yes | Requires `offline.access`. |

For media, Dravo initializes the upload, appends chunks, finalizes it and polls
processing when X responds asynchronously. A post carries either up to 4 images,
or a single video or GIF (images and video cannot be mixed).

### Post options (`platform_options.x`)

```json
{
  "account_ids": ["YOUR_X_ACCOUNT_ID"],
  "text": "What should we build next?",
  "platform_options": {
    "x": {
      "poll": { "options": ["Feature A", "Feature B"], "duration_minutes": 1440 },
      "reply_settings": "following",
      "made_with_ai": false
    }
  }
}
```

| Option | Type | Meaning |
| --- | --- | --- |
| `poll` | object | `{ options: [2 to 4 strings], duration_minutes: 5 to 10080 }`. Cannot be combined with media. |
| `reply_settings` | string | Who can reply: `following`, `mentionedUsers`, `subscribers` or `verified` (default everyone). |
| `reply_to` | string | Post id to reply to (for threads). |
| `place_id` | string | Tag a location by X place id. |
| `for_super_followers_only` | boolean | Restrict to Super Followers. |
| `made_with_ai` | boolean | Label the media as AI-generated. |
| `first_comment` | string | Auto-reply to the new post with this text (best effort). Any failure appears in the result's `warnings`. |

## Troubleshooting

### `invalid_client` during callback

1. Confirm the Dravo Client ID came from **OAuth 2.0**, not API Key.
2. Confirm the Client Secret belongs to the same app.
3. Confirm the app type is Web App/Automated App/Bot.
4. If the secret was regenerated, update it in Dravo and reconnect.

### Callback or redirect mismatch

Open `App → Settings → User authentication settings → Edit` and compare the
stored callback character by character with Dravo's fixed URI.

### Creating a post returns 403

1. Confirm App permissions is **Read and write**.
2. Confirm the token contains `tweet.write`.
3. Confirm the X project has sufficient API credits/access.
4. Reconnect after fixing permissions.

### Text works but media fails

1. Confirm `media.write` appears on the connected account.
2. Confirm the token was issued after that scope was added.
3. Confirm media API usage is funded.
4. Reconnect and retry with a small image.

### X analytics are empty or partial

1. Confirm the connected token includes `tweet.read` and `users.read`.
2. Confirm the post belongs to the authenticated account when requesting
   non-public or organic metrics.
3. Remember that X documents a limited window for non-public, organic and
   promoted metrics.
4. Check API access/credits before changing OAuth; public metrics can work while
   private metric fields are unavailable.

### The account expires after about two hours

The token lacks `offline.access` or X did not issue a refresh token. Confirm the
scope appears in Dravo, revoke the old consent and reconnect.

## Official documentation

- [Get access to the X Developer Console](https://docs.x.com/x-api/getting-started/getting-access)
- [OAuth 2.0 Authorization Code Flow with PKCE](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code)
- [X API authentication overview](https://docs.x.com/fundamentals/authentication/overview)
- [X API metrics](https://docs.x.com/x-api/fundamentals/metrics)
- [Chunked media upload](https://docs.x.com/x-api/media/quickstart/media-upload-chunked)
- [X API pay-per-usage pricing](https://docs.x.com/x-api/getting-started/pricing)

Next, read [Configure OAuth and connect accounts](/docs/guides/connect-accounts)
for the shared browser flow and [Posts](/docs/api/posts) for the unified post
payload.
