PHPackages                             axsag/opentweet - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. axsag/opentweet

ActiveLibrary[API Development](/categories/api)

axsag/opentweet
===============

PHP 8+ SDK for the OpenTweet API — schedule, publish and analyse X/Twitter posts.

v1.1.0(2mo ago)14MITPHPPHP ^8.1

Since May 7Pushed 2mo agoCompare

[ Source](https://github.com/Axsag/opentweet-php)[ Packagist](https://packagist.org/packages/axsag/opentweet)[ Docs](https://github.com/Axsag/opentweet-php)[ RSS](/packages/axsag-opentweet/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (2)Dependencies (7)Versions (3)Used By (0)

OpenTweet PHP SDK
=================

[](#opentweet-php-sdk)

A modern, fully-typed PHP 8.1+ SDK for the [OpenTweet](https://opentweet.io) API.
Schedule, publish, and analyse X/Twitter posts from your PHP application.

[![PHP](https://camo.githubusercontent.com/cc9cdea9aa96b40a822425e981b0a030e3371202973c7d57b74e8e99834f81dc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253545382e312d626c7565)](https://www.php.net)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)

---

Requirements
------------

[](#requirements)

RequirementVersionPHP^8.1PSR-18 HTTP clientany (Guzzle 7, Symfony HTTP Client, …)PSR-17 HTTP factoriesany (shipped with Guzzle / Symfony)---

Installation
------------

[](#installation)

```
composer require Axsag/opentweet
```

The SDK is **HTTP-client agnostic**. Install whichever PSR-18 implementation you prefer:

```
# Guzzle (recommended)
composer require guzzlehttp/guzzle

# Or Symfony HTTP Client
composer require symfony/http-client nyholm/psr7
```

---

Quick Start
-----------

[](#quick-start)

```
use OpenTweet\OpenTweet;
use OpenTweet\Enums\PostStatus;

$client = new OpenTweet(apiKey: 'ot_your_api_key_here');

// Create a post and publish it immediately
$post = $client->posts()->create('Hello from PHP!');
$client->posts()->publish($post->id);

// Schedule a post
$client->posts()->schedule($post->id, new DateTimeImmutable('2026-03-01 10:00:00'));

// List all scheduled posts
$collection = $client->posts()->list(status: PostStatus::Scheduled);

foreach ($collection as $post) {
    echo "{$post->id}: {$post->text}" . PHP_EOL;
}
```

---

Authentication
--------------

[](#authentication)

Pass your API key to the constructor. It is sent as a `Bearer` token on every request.

```
$client = new OpenTweet(apiKey: 'ot_your_api_key_here');
```

---

Bring Your Own HTTP Client
--------------------------

[](#bring-your-own-http-client)

Pass any PSR-18 / PSR-17 implementations directly:

```
use GuzzleHttp\Client as Guzzle;
use GuzzleHttp\Psr7\HttpFactory;

$factory = new HttpFactory();

$client = new OpenTweet(
    apiKey:         'ot_your_api_key_here',
    httpClient:     new Guzzle(['timeout' => 10]),
    requestFactory: $factory,
    streamFactory:  $factory,
);
```

If you don't pass any, the SDK will auto-detect Guzzle or Symfony HTTP Client.

---

Posts
-----

[](#posts)

### List posts

[](#list-posts)

```
use OpenTweet\Enums\PostStatus;

// All posts (first page, 20 per page)
$collection = $client->posts()->list();

// Paginate
$collection = $client->posts()->list(page: 2, limit: 50);

// Filter by status
$scheduled  = $client->posts()->list(status: PostStatus::Scheduled);
$posted     = $client->posts()->list(status: PostStatus::Posted);

// Iterate
foreach ($collection as $post) {
    echo $post->text . PHP_EOL;
}

// Pagination metadata
$collection->pagination->total;      // int — total items
$collection->pagination->pages;      // int — total pages
$collection->pagination->hasNextPage();     // bool
$collection->pagination->hasPreviousPage(); // bool
```

### Fetch a single post

[](#fetch-a-single-post)

```
$post = $client->posts()->find('abc123');

echo $post->id;            // string
echo $post->text;          // string
echo $post->category;      // string
echo $post->isThread;      // bool
echo $post->status->value; // 'draft' | 'scheduled' | 'posted' | 'failed'
echo $post->scheduledDate?->format('Y-m-d H:i'); // ?string (UTC)
echo $post->xPostId;       // ?string — set after publication
```

### Create a post

[](#create-a-post)

```
// Basic
$post = $client->posts()->create('Hello world!');

// With category
$post = $client->posts()->create('Hello world!', category: 'Tech');

// Pre-scheduled
$post = $client->posts()->create(
    text:          'Scheduled at creation!',
    scheduledDate: new DateTimeImmutable('2026-03-01 10:00:00'),
);
```

### Create multiple posts at once

[](#create-multiple-posts-at-once)

```
$posts = $client->posts()->createMany([
    ['text' => 'First tweet',  'category' => 'Tech'],
    ['text' => 'Second tweet', 'category' => 'General'],
]);
```

### Create a thread

[](#create-a-thread)

```
$post = $client->posts()->createThread(
    intro:        'Thread intro — here is what I learned this week:',
    continuation: [
        '1/ First insight…',
        '2/ Second insight…',
        '3/ Conclusion — thanks for reading!',
    ],
    category: 'Tech',
);
```

### Update a post

[](#update-a-post)

Only unpublished posts can be edited.

```
$post = $client->posts()->update(
    id:       'abc123',
    text:     'Updated tweet text!',
    category: 'Tech',
);
```

### Delete a post

[](#delete-a-post)

```
// Delete post and its X/Twitter counterpart
$client->posts()->delete('abc123');

// Delete post only (keep the X/Twitter post)
$client->posts()->delete('abc123', deleteFromX: false);
```

### Schedule a post

[](#schedule-a-post)

Requires an active OpenTweet subscription.

```
$post = $client->posts()->schedule(
    id:            'abc123',
    scheduledDate: new DateTimeImmutable('2026-03-01 10:00:00'),
);
```

### Publish a post immediately

[](#publish-a-post-immediately)

Requires an active subscription and a connected X account.

```
$post = $client->posts()->publish('abc123');

echo $post->xPostId; // X/Twitter post ID
```

### Batch-schedule up to 50 posts

[](#batch-schedule-up-to-50-posts)

```
$posts = $client->posts()->batchSchedule([
    [
        'post_id'        => 'abc123',
        'scheduled_date' => new DateTimeImmutable('2026-03-01 10:00:00'),
    ],
    [
        'post_id'        => 'def456',
        'scheduled_date' => new DateTimeImmutable('2026-03-01 14:00:00'),
    ],
]);
```

---

Upload
------

[](#upload)

Upload images (max 5 MB) or videos (max 20 MB) and receive a hosted URL.

**Supported formats:** JPG, PNG, GIF, WebP, MP4, MOV.

```
$url = $client->upload()->file('/path/to/photo.jpg');

// Use the URL when creating a post (pass it in your text or as metadata)
$post = $client->posts()->create("Check this out! {$url}");
```

The SDK validates the file extension and size locally before making the HTTP call.

---

Analytics
---------

[](#analytics)

### Account overview

[](#account-overview)

```
$overview = $client->analytics()->overview();
// Returns raw array: posting stats, streaks, trends, categories
```

### Tweet engagement (Advanced plan)

[](#tweet-engagement-advanced-plan)

```
use OpenTweet\Enums\AnalyticsPeriod;

$metrics = $client->analytics()->tweets(AnalyticsPeriod::Month); // default
$metrics = $client->analytics()->tweets(AnalyticsPeriod::Week);
$metrics = $client->analytics()->tweets(AnalyticsPeriod::Year);
$metrics = $client->analytics()->tweets(AnalyticsPeriod::All);
```

### Best posting times

[](#best-posting-times)

```
$best = $client->analytics()->bestTimes();
```

---

Error Handling
--------------

[](#error-handling)

All exceptions extend `OpenTweet\Exceptions\OpenTweetException`.

```
use OpenTweet\Exceptions\AuthenticationException;
use OpenTweet\Exceptions\BadRequestException;
use OpenTweet\Exceptions\ForbiddenException;
use OpenTweet\Exceptions\NotFoundException;
use OpenTweet\Exceptions\OpenTweetException;
use OpenTweet\Exceptions\RateLimitException;
use OpenTweet\Exceptions\XApiException;

try {
    $post = $client->posts()->publish('abc123');
} catch (RateLimitException $e) {
    // Respect the suggested back-off
    sleep($e->retryAfter);
} catch (AuthenticationException $e) {
    // Invalid or missing API key
} catch (ForbiddenException $e) {
    // Subscription required, or feature needs Advanced plan
} catch (NotFoundException $e) {
    // Post not found or not owned by this account
} catch (XApiException $e) {
    // X/Twitter API rejected the publish request
} catch (BadRequestException $e) {
    // Invalid parameters sent to the API
} catch (OpenTweetException $e) {
    // Catch-all for any other API error
}
```

### Exception reference

[](#exception-reference)

ExceptionHTTP statusMeaning`BadRequestException`400Invalid parameters`AuthenticationException`401Missing or invalid API key`ForbiddenException`403Subscription required / Advanced plan feature`NotFoundException`404Post not found or not owned by you`RateLimitException`429Too many requests — check `$e->retryAfter``XApiException`502X/Twitter API error during publish---

Data Transfer Objects
---------------------

[](#data-transfer-objects)

### `Post`

[](#post)

PropertyTypeDescription`$id``string`Unique post ID`$text``string`Tweet text`$category``string`User-defined category`$isThread``bool`Thread starter flag`$scheduledDate``?DateTimeImmutable`Scheduled UTC time`$postedDate``?DateTimeImmutable`Published UTC time`$status``?PostStatus`Lifecycle status enum`$reviewStatus``?string`Review status string`$xPostId``?string`X/Twitter post ID`$createdAt``DateTimeImmutable`Record creation time### `PostCollection`

[](#postcollection)

Returned by `Posts::list()`. Implements `Countable` and `IteratorAggregate`.

```
count($collection);          // int — posts on this page
$collection->pagination;     // Pagination DTO
foreach ($collection as $post) { ... }
```

### `Pagination`

[](#pagination)

PropertyTypeDescription`$page``int`Current page`$limit``int`Items per page`$total``int`Total items`$pages``int`Total pagesHelper methods: `hasNextPage()`, `hasPreviousPage()`.

---

Testing
-------

[](#testing)

```
composer test       # Run Pest test suite
composer analyse    # PHPStan static analysis (level 8)
composer cs         # PSR-12 code style check
```

---

License
-------

[](#license)

MIT © Axsag

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance85

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

79d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/37663338?v=4)[axsag](/maintainers/Axsag)[@Axsag](https://github.com/Axsag)

---

Tags

apisdktwitterschedulingxopentweet

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/axsag-opentweet/health.svg)

```
[![Health](https://phpackages.com/badges/axsag-opentweet/health.svg)](https://phpackages.com/packages/axsag-opentweet)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k16](/packages/tempest-framework)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

36789.4k2](/packages/telnyx-telnyx-php)[openai-php/client

OpenAI PHP is a supercharged PHP API client that allows you to interact with the Open AI API

5.8k28.0M326](/packages/openai-php-client)[mollie/mollie-api-php

Mollie API client library for PHP. Mollie is a European Payment Service provider and offers international payment methods such as Mastercard, VISA, American Express and PayPal, and local payment methods such as iDEAL, Bancontact, SOFORT Banking, SEPA direct debit, Belfius Direct Net, KBC Payment Button and various gift cards such as Podiumcadeaukaart and fashioncheque.

60316.0M89](/packages/mollie-mollie-api-php)[getbrevo/brevo-php

Official Brevo provided RESTFul API V3 php library

1003.9M50](/packages/getbrevo-brevo-php)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

85036.3k](/packages/flow-php-flow)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
