> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xquik.com/llms.txt
> Use this file to discover all available pages before exploring further.

# X (Twitter) API Java media tweets & DM attachments

> Upload media with the Xquik Java SDK, post tweets with photos or video, and send DM attachments from a connected X (Twitter) account in your own apps and bots.

> For the complete documentation index, see [llms.txt](https://docs.xquik.com/llms.txt).

This guide builds on the [Java SDK setup](https://docs.xquik.com/sdks/java.md), which covers install and authentication.

## Workflow to post media tweets and DM attachments

Use this workflow to post a media tweet or reply. It can also DM an uploaded local file. `client.x().tweets().create` maps to `POST /x/tweets`. Pass public media URLs through `.addMedia()` or `.media()`. Send up to 4 image URLs or exactly 1 MP4 video URL. The video can be up to 100 MB. Do not mix video with other media. For replies, set `.replyToTweetId()` to the parent tweet ID. `client.x().media().upload` maps to `POST /x/media`. Use `media.mediaId()` only for the one-item DM `.addMediaId()` handoff.

```java theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/bash.json","/languages/csharp.json","/languages/go.json","/languages/java.json","/languages/javascript.json","/languages/kotlin.json","/languages/php.json","/languages/python.json","/languages/ruby.json","/languages/typescript.json"]}}
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.x_twitter_scraper.api.client.XTwitterScraperClient;
import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient;
import com.x_twitter_scraper.api.core.http.HttpResponseFor;
import com.x_twitter_scraper.api.models.x.dm.DmSendParams;
import com.x_twitter_scraper.api.models.x.dm.DmSendResponse;
import com.x_twitter_scraper.api.models.x.media.MediaUploadParams;
import com.x_twitter_scraper.api.models.x.media.MediaUploadResponse;
import com.x_twitter_scraper.api.models.x.tweets.TweetCreateParams;
import com.x_twitter_scraper.api.models.x.tweets.TweetCreateResponse;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

XTwitterScraperClient client = XTwitterScraperOkHttpClient.fromEnv();
ObjectMapper objectMapper = new ObjectMapper();

static Map<String, Object> createTweetHandoff(
    Map<String, Object> payload,
    Map<String, Object> base
) {
    Map<String, Object> handoff = new LinkedHashMap<>();
    Map<String, Object> billing = (Map<String, Object>) payload.get("billing");
    Map<String, Object> request = (Map<String, Object>) payload.get("request");
    Map<String, Object> result = (Map<String, Object>) payload.get("result");
    boolean terminal = Boolean.TRUE.equals(payload.get("terminal"));

    handoff.put("status", payload.get("status"));
    handoff.put("terminal", terminal);
    handoff.put("safe_to_retry", payload.get("safeToRetry"));
    handoff.put("write_action_id", payload.get("id"));
    handoff.put("request_hash", request.get("hash"));
    handoff.put("tweet_id", result == null ? payload.get("tweetId") : result.get("id"));
    handoff.put("charged", billing.get("charged"));
    handoff.put("charged_credits", billing.get("chargedCredits"));
    handoff.put("poll", terminal ? null : payload.get("statusUrl"));

    handoff.putAll(base);
    return handoff;
}

Map<String, Object> tweetPayload;
try (HttpResponseFor<TweetCreateResponse> response = client.x().tweets().withRawResponse().create(
    TweetCreateParams.builder()
        .account("@username")
        .text("Shipping the weekly X API video.")
        .addMedia("https://static.example.com/reports/x-api-export.mp4")
        .build()
)) {
    tweetPayload = objectMapper.readValue(
        response.body(),
        new TypeReference<Map<String, Object>>() {}
    );
}

Map<String, Object> tweetBase = new LinkedHashMap<>();
tweetBase.put("account", "@username");
tweetBase.put("media", List.of("https://static.example.com/reports/x-api-export.mp4"));
Map<String, Object> tweetHandoff = createTweetHandoff(tweetPayload, tweetBase);

Map<String, Object> replyPayload;
try (HttpResponseFor<TweetCreateResponse> response = client.x().tweets().withRawResponse().create(
    TweetCreateParams.builder()
        .account("@username")
        .text("Here is the chart behind the update.")
        .addMedia("https://static.example.com/reports/reply-chart.png")
        .replyToTweetId("1893704267862470862")
        .build()
)) {
    replyPayload = objectMapper.readValue(
        response.body(),
        new TypeReference<Map<String, Object>>() {}
    );
}

Map<String, Object> replyBase = new LinkedHashMap<>();
replyBase.put("account", "@username");
replyBase.put("reply_to_tweet_id", "1893704267862470862");
replyBase.put("media", List.of("https://static.example.com/reports/reply-chart.png"));
Map<String, Object> replyHandoff = createTweetHandoff(replyPayload, replyBase);

MediaUploadResponse media = client.x().media().upload(
    MediaUploadParams.builder()
        .account("@username")
        .file(Paths.get("handoff.png"))
        .build()
);

DmSendResponse dm = client.x().dm().send(
    "44196397",
    DmSendParams.builder()
        .account("@username")
        .text("Here is the requested asset.")
        .addMediaId(media.mediaId())
        .build()
);

Map<String, Object> dmHandoff = new LinkedHashMap<>();
dmHandoff.put("status", "sent");
dmHandoff.put("message_id", dm.messageId());
dmHandoff.put("media_id", media.mediaId());
dmHandoff.put("account", "@username");
dmHandoff.put("user_id", "44196397");

System.out.println(objectMapper.writeValueAsString(tweetHandoff));
System.out.println(objectMapper.writeValueAsString(replyHandoff));
System.out.println(objectMapper.writeValueAsString(dmHandoff));
```

Use `client.x().tweets().withRawResponse().create(...)` to capture the durable write action. Send a unique `Idempotency-Key`. Store `id`, `request.hash`, `billing`, `result`, and `statusUrl`. Poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key.




Keep DM body text in private systems. Shared logs and handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and lifecycle status. Leave `replyToMessageId` unset because the REST endpoint rejects DM reply threading.




`client.x().dm().deleteMessage` [deletes a DM](https://docs.xquik.com/api-reference/x-write/delete-dm.md).




Use public media URLs with `client.x().tweets().create`. Do not pass uploaded `media.mediaId()` values to tweet creation.

### REST routes

| Task | REST route | Reference |
| - | - | - |
| Post a media tweet or reply | `POST /x/tweets` | [Create tweet](https://docs.xquik.com/api-reference/x-write/create-tweet.md) |
| Upload a local file | `POST /x/media` | [Upload media](https://docs.xquik.com/api-reference/x-write/upload-media.md) |
| Send a DM attachment | `POST /x/dm/{userId}` | [Send DM](https://docs.xquik.com/api-reference/x-write/send-dm.md) |
| Check a write action | `GET /x/write-actions/{id}` | [Write action status](https://docs.xquik.com/api-reference/x-write/get-write-action-status.md) |
| Delete a DM | `DELETE /x/dm/{userId}/messages/{messageId}` | [Delete DM](https://docs.xquik.com/api-reference/x-write/delete-dm.md) |

## Next steps

* [Java SDK setup](https://docs.xquik.com/sdks/java.md)
* [Tweet search](https://docs.xquik.com/sdks/java-tweet-search.md)
* [Follower export](https://docs.xquik.com/sdks/java-follower-export.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.