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.
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';PHPand need to be changed to
private const API_HOST = 'https://api.x.com';
private const UPLOAD_HOST = 'https://api.x.com';PHPThe 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,PHPSetting 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

Leave a Reply