Gravid Banner

Posting to Twitter using X API v2

Having successfully posted to Instagram using their API, I also wanted to post to X. I knew from memory that this would not be so easy. X uses oauth authentication, and in the past I have found this to be particularly problematic.

A bit of searching turned up a couple of useful videos on youtube. The first, showing how to post a text-only tweet works a treat. Unfortunately the follow-up showing how to post a media tweet uses Twitter API v1.1 for the media upload. That was deprecated a while back and stopped working in March 2025. The basic code looks sound, and you would think that just updating the 1.1 to 2 would work. Sadly it doesn’t.

In the process of trying to fix the issue I ran into a whole load of errors (400 – Bad Request, 403 – Forbidden, 410 – Gone, 431 – Request Headers Too Large amongst others).

A lot of debugging tracked this down to a handful of lines in the Abraham Twitter Oauth library. This was written for API 1.0, and 1.1, and has been under maintenance only support for a couple of years, meaning that it has some issues connecting to some endpoints in the v2 API. Media uploads being amongst them.

OAuth 2 in Action (Ad)

The fix for me was on two main areas in the src/TwitterOauth.php file. The first is right at the top (lines 29-30 in the version I downloaded).

These currently say

private const API_HOST = 'https://api.twitter.com';
private const UPLOAD_HOST = 'https://upload.twitter.com';
PHP

and need to be changed to

private const API_HOST = 'https://api.x.com';
private const UPLOAD_HOST = 'https://api.x.com';
PHP

The second area is in the functions uploadMediaNotChunked and uploadMediaChunked which sit between lines 355 and 424. In these there are four locations which set jsonPayload to false. This puts the media upload in the http header and gives the 431 Request Headers too large error.

'jsonPayload' => true,
PHP

Setting this true gets around the problem.

Please note I have only tested this with unchunked uploads. I assume it is also required for chunked, but I note from the issues list that ther may be other issues lurking there.

require 'vendor/autoload.php';
use Abraham\TwitterOAuth\TwitterOAuth;

class twAPI {
  private $prefix = "https://api.x.com/2/";
  private $apiKey = "xxx";            // your api key
  private $apiSecret = "xxx";         // your api secret
  private $accessToken = "xxx";       // your access token
  private $accessTokenSecret = "xxx"; // your secret token
  private $bearerToken = "xxx";       // unused
  
  private $conn = false;
  private $status = 0;

  function __construct() {
    $this->conn = new TwitterOAuth(
      $this->apiKey, 
      $this->apiSecret, 
      $this->accessToken, 
      $this->accessTokenSecret);
    
    // use the v2 api  
    $this->conn->setApiVersion('2');
  }
    
  function tweet($text, $mediaId=0) {
    if ($this->conn !== false) {
      $tweet['text'] = $text;
      if ($mediaId != 0)
        $tweet['media']['media_ids'][] = $mediaId;
    
      $response = $this->conn->post('tweets', $tweet);

      // Check response and output result
      if (isset($response->data->id)) {
        return true;
      } else {
        // deal with error
      }
  }
  
  function uploadMedia($path) {
    $arr =['media'=>$path,
           'media_category'=>'tweet_image'];
    
    if ($this->conn !== false) {
      $response = $this->conn->upload('media/upload', $arr);
      if ($this->conn->getLastHttpCode() == 200) {
         return $response->data->id;
      }
    }
    return false;
  }
  
  function mediaTweet($text, $path) {
    $id = $this->uploadMedia($path);
    if ($id !== false) $this->tweet ($text, $id);
  }
}
PHP

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *