PHPackages                             avansaber/php-reddit-api - 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. avansaber/php-reddit-api

ActiveLibrary

avansaber/php-reddit-api
========================

Modern, fluent, framework-agnostic Reddit API client for PHP (PSR-18/PSR-7).

v0.1.0(4mo ago)19819131MITPHPPHP ^8.1CI passing

Since Jan 13Pushed 4mo ago22 watchersCompare

[ Source](https://github.com/avansaber/avansaber-php-reddit-api)[ Packagist](https://packagist.org/packages/avansaber/php-reddit-api)[ RSS](/packages/avansaber-php-reddit-api/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (11)Versions (2)Used By (0)

avansaber/php-reddit-api

Modern, fluent, framework-agnostic Reddit API client for PHP (PSR-18/PSR-7/PSR-3).

[![CI](https://github.com/avansaber/avansaber-php-reddit-api/actions/workflows/ci.yml/badge.svg)](https://github.com/avansaber/avansaber-php-reddit-api/actions/workflows/ci.yml/badge.svg)[![Packagist](https://camo.githubusercontent.com/6fa1dbeca60acc86c694f708277e5d13fc9797d8225d7f9dae1da6cac897bd27/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6176616e73616265722f7068702d7265646469742d6170692e737667)](https://packagist.org/packages/avansaber/php-reddit-api)[![Downloads](https://camo.githubusercontent.com/123a78132027c03fcd7afe07463b807e932b48fc1309e3aaca69c51971f3474e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6176616e73616265722f7068702d7265646469742d6170692e737667)](https://packagist.org/packages/avansaber/php-reddit-api)

Features

- PSR-18 HTTP client and PSR-7/17 factories (bring your own client)
- Typed DTOs: Link, Comment, User, Subreddit, Message, Flair
- Resources: me, search, subreddit (with listings), user, comments, messages, moderation, flair
- Write actions: vote, reply, submit posts, edit, delete, save/unsave, hide/unhide
- Private messages: inbox, sent, unread, compose, markRead
- Moderation: approve, remove, lock/unlock, sticky, distinguish, NSFW/spoiler marking
- Token storage with optional SQLite + sodium encryption
- CSRF protection helpers for OAuth (state parameter validation)
- Auto-refresh tokens on 401
- Retries/backoff for 429/5xx

Requirements

- PHP 8.1+
- Any PSR-18 HTTP client and PSR-7/17 factories (auto-discovered via `php-http/discovery`)

Installation

```
composer require avansaber/php-reddit-api
```

To run the examples, install a PSR-18 client implementation (discovery will find it):

```
composer require php-http/guzzle7-adapter guzzlehttp/guzzle
```

Getting Reddit API credentials

- Log in to Reddit, open `https://www.reddit.com/prefs/apps`.
- Click “create another app”.
- For app-only reads, choose type “script”. For end-user auth, choose “web app” (Authorization Code) and set a valid redirect URI.
- Fill name and description, then create.
- Copy:
    - Client ID: the short string directly under your app name (next to the app icon). For “personal use script” apps this is a 14‑character string shown under the app name.
    - Client Secret: the value labeled “secret” on the app page (not present for “installed” apps).
- Provide a descriptive User-Agent per Reddit policy, e.g. `yourapp/1.0 (by yourdomain.com; contact you@example.com)`.

Quickstart

```
use Avansaber\RedditApi\Config\Config;
use Avansaber\RedditApi\Http\RedditApiClient;
use Http\Discovery\Psr17FactoryDiscovery;
use Http\Discovery\Psr18ClientDiscovery;

$config = new Config('avansaber-php-reddit-api/1.0; contact you@example.com');
$http = Psr18ClientDiscovery::find();
$psr17 = Psr17FactoryDiscovery::findRequestFactory();
$streamFactory = Psr17FactoryDiscovery::findStreamFactory();

$client = new RedditApiClient($http, $psr17, $streamFactory, $config);
$client->withToken('YOUR_ACCESS_TOKEN');
$me = $client->me()->get();
```

Authentication

- App-only (client credentials) for read endpoints like search: ```
    ```

use Avansaber\\RedditApi\\Auth\\Auth; use Avansaber\\RedditApi\\Config\\Config; use Http\\Discovery\\Psr18ClientDiscovery; use Http\\Discovery\\Psr17FactoryDiscovery;

$http = Psr18ClientDiscovery::find(); $psr17 = Psr17FactoryDiscovery::findRequestFactory(); $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); $config = new Config('yourapp/1.0 (by yourdomain.com; contact )'); $auth = new Auth($http, $psr17, $streamFactory, $config); $accessToken = $auth-&gt;appOnly('CLIENT\_ID', 'CLIENT\_SECRET', \['read','identity'\]);

```
- Using an existing user token:
- Set `REDDIT_ACCESS_TOKEN` and run `examples/me.php`.
Authorization Code + PKCE (with CSRF protection)
- Generate PKCE pair, state parameter, and build the authorize URL. Validate state on callback:
```php
use Avansaber\RedditApi\Auth\Auth;
use Avansaber\RedditApi\Config\Config;
use Http\Discovery\Psr18ClientDiscovery; use Http\Discovery\Psr17FactoryDiscovery;

$http = Psr18ClientDiscovery::find();
$psr17 = Psr17FactoryDiscovery::findRequestFactory();
$streamFactory = Psr17FactoryDiscovery::findStreamFactory();
$config = new Config('yourapp/1.0 (by yourdomain.com; contact you@example.com)');
$auth = new Auth($http, $psr17, $streamFactory, $config);

// Generate PKCE pair and CSRF state token
$pkce = $auth->generatePkcePair(); // ['verifier' => '...', 'challenge' => '...']
$state = Auth::generateState(); // Secure random hex string

// Store state and verifier in session for later validation
$_SESSION['oauth_state'] = $state;
$_SESSION['oauth_verifier'] = $pkce['verifier'];

$url = $auth->getAuthUrl('CLIENT_ID', 'https://yourapp/callback', ['identity','read','submit'], $state, $pkce['challenge']);
// Redirect user to $url

// In your callback handler:
try {
  Auth::validateState($_SESSION['oauth_state'], $_GET['state']); // Throws on mismatch
} catch (\InvalidArgumentException $e) {
  die('CSRF validation failed');
}
$tokens = $auth->getAccessTokenFromCode('CLIENT_ID', null, $_GET['code'], 'https://yourapp/callback', $_SESSION['oauth_verifier']);
// $tokens contains access_token, refresh_token, expires_in, scope

```

Temporary manual Authorization Code exchange (for testing)

```
# After you obtain ?code=... from the authorize redirect
curl -A "macos:avansaber-php-reddit-api:0.1 (by /u/YourRedditUsername)" \
  -u 'CLIENT_ID:CLIENT_SECRET' \
  -d 'grant_type=authorization_code&code=PASTE_CODE&redirect_uri=http://localhost:8080/callback' \
  https://www.reddit.com/api/v1/access_token

export REDDIT_USER_AGENT="macos:avansaber-php-reddit-api:0.1 (by /u/YourRedditUsername)"
export REDDIT_ACCESS_TOKEN=PASTE_ACCESS_TOKEN
php examples/me.php
```

Scopes

- Reddit uses space-separated scopes when requesting tokens. Common scopes used by this package:

ScopeDescriptionUsed byidentityVerify the current user`me()`readRead public data`search()`, `subreddit()`, `user()`, `comments()`voteVote on posts and comments`links()->upvote()`, `downvote()`, `unvote()`submitSubmit links or comments`links()->submitText()`, `submitLink()`, `reply()`editEdit posts and comments`links()->edit()`saveSave/unsave content`links()->save()`, `unsave()`privatemessagesSend/read private messages`messages()->inbox()`, `sent()`, `compose()`, `markRead()`subscribeSubscribe to subreddits`subreddit()->subscribe()`, `unsubscribe()`modpostsModerate posts/comments`moderation()->approve()`, `remove()`, `lock()`, `sticky()`flairManage flair`flair()->setLinkFlair()`, `setUserFlair()`Common usage

- Search posts: ```
    ```

$listing = $client-&gt;search()-&gt;get('php', \['limit' =&gt; 5, 'sort' =&gt; 'relevance'\]); foreach ($listing-&gt;items as $post) { echo $post-&gt;title . "\\n"; }

```
- Pagination helper example:
```php
$first = $client->search()->get('php', ['limit' => 100]);
foreach ($first->iterate(fn($after) => $client->search()->get('php', ['limit' => 100, 'after' => $after])) as $post) {
  // handle $post (Link DTO) across multiple pages
}

```

- User history (comments/submitted): ```
    ```

$comments = $client-&gt;user()-&gt;comments('spez', \['limit' =&gt; 10\]); $posts = $client-&gt;user()-&gt;submitted('spez', \['limit' =&gt; 10\]);

```
- Subreddit info: `$sr = $client->subreddit()->about('php');`
- User info: `$u = $client->user()->about('spez');`
- Voting and replying (requires user-context token with proper scopes):
```php
$client->links()->upvote('t3_abc123');
$comment = $client->links()->reply('t3_abc123', 'Nice post!');

```

- Submit a text post: ```
    ```

$post = $client-&gt;links()-&gt;submitText('test', 'My Post Title', 'Post body here', \[ 'flair\_id' =&gt; 'optional-flair-id', 'nsfw' =&gt; false, \]);

```
- Get comments on a post:
```php
$result = $client->comments()->get('php', 'abc123', ['sort' => 'top', 'limit' => 50]);
// $result['post'] is a Link DTO, $result['comments'] is an array of Comment DTOs

```

- Subreddit listings (hot, new, top, rising): ```
    ```

$hot = $client-&gt;subreddit()-&gt;hot('php', \['limit' =&gt; 25\]); $top = $client-&gt;subreddit()-&gt;top('php', \['t' =&gt; 'week', 'limit' =&gt; 10\]);

```
- Subscribe/unsubscribe:
```php
$client->subreddit()->subscribe('php');
$client->subreddit()->unsubscribe('php');

```

- Private messages: ```
    ```

$inbox = $client-&gt;messages()-&gt;inbox(\['limit' =&gt; 10\]); $client-&gt;messages()-&gt;compose('username', 'Subject', 'Message body'); $client-&gt;messages()-&gt;markRead(\['t4\_abc123', 't4\_def456'\]);

```
- Moderation:
```php
$client->moderation()->approve('t3_abc123');
$client->moderation()->remove('t3_abc123', spam: true);
$client->moderation()->lock('t3_abc123');
$client->moderation()->sticky('t3_abc123', num: 1);
$client->moderation()->distinguish('t1_comment', 'yes', sticky: true);

```

- Flair management: ```
    ```

$flairs = $client-&gt;flair()-&gt;getLinkFlairs('subreddit'); $client-&gt;flair()-&gt;setLinkFlair('subreddit', 't3\_abc123', $flairs\[0\]-&gt;id);

```

Rate limiting and retries
- Reddit returns `x-ratelimit-remaining`, `x-ratelimit-used`, `x-ratelimit-reset` headers.
- The client parses these headers and exposes the latest via `$client->getLastRateLimitInfo()`.
- The client retries 429/5xx with exponential backoff and respects `Retry-After` when present.

Error handling
- Methods throw `Avansaber\RedditApi\Exceptions\RedditApiException` on non-2xx.
- You can inspect `getStatusCode()` and `getResponseBody()` for details.

HTTP client setup
- By default we use discovery to find a PSR-18 client and PSR-7/17 factories.
- Alternatively, install and wire your own (e.g., Guzzle + Nyholm PSR-7) and pass them to the constructor.

### Framework integration (Laravel, CodeIgniter, etc.)
- Works in any framework as long as a PSR-18 client and PSR-7/17 factories are available (use discovery):
```php
use Avansaber\RedditApi\Config\Config;
use Avansaber\RedditApi\Http\RedditApiClient;
use Http\Discovery\Psr18ClientDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;

$http = Psr18ClientDiscovery::find();
$psr17 = Psr17FactoryDiscovery::findRequestFactory();
$streams = Psr17FactoryDiscovery::findStreamFactory();
$config = new Config(getenv('REDDIT_USER_AGENT'));
$client = new RedditApiClient($http, $psr17, $streams, $config);

```

- Laravel bridge (first-class)

    - Repository:
    - Install: ```
        composer require avansaber/avansaber-laravel-reddit-api
        ```
    - Publish config and migrations: ```
        php artisan vendor:publish --tag=config --provider="Avansaber\\LaravelRedditApi\\RedditApiServiceProvider"
        php artisan vendor:publish --tag=migrations --provider="Avansaber\\LaravelRedditApi\\RedditApiServiceProvider"
        php artisan migrate
        ```
    - OAuth routes provided: `/reddit/connect`, `/reddit/callback`
    - Env keys to set: `REDDIT_CLIENT_ID`, `REDDIT_CLIENT_SECRET`, `REDDIT_REDIRECT_URI`, `REDDIT_USER_AGENT`, `REDDIT_SCOPES`
    - After connecting, tokens are stored in `reddit_tokens`. Resolve `Avansaber\RedditApi\Http\RedditApiClient` from the container and call APIs.
- Laravel (manual wiring, if not using the bridge)

    - In `App\Providers\AppServiceProvider` → `register()`: ```
        ```

use Avansaber\\RedditApi\\Config\\Config; use Avansaber\\RedditApi\\Http\\RedditApiClient; use Http\\Discovery\\Psr18ClientDiscovery; use Http\\Discovery\\Psr17FactoryDiscovery;

public function register(): void { $this-&gt;app-&gt;singleton(RedditApiClient::class, function () { $http = Psr18ClientDiscovery::find(); $psr17 = Psr17FactoryDiscovery::findRequestFactory(); $streams = Psr17FactoryDiscovery::findStreamFactory(); $config = new Config(config('services.reddit.user\_agent')); return new RedditApiClient($http, $psr17, $streams, $config); }); } ```

- In `config/services.php`: ```
    ```

'reddit' =&gt; \[ 'client\_id' =&gt; env('REDDIT\_CLIENT\_ID'), 'client\_secret' =&gt; env('REDDIT\_CLIENT\_SECRET'), 'user\_agent' =&gt; env('REDDIT\_USER\_AGENT'), \], ```

- Example usage in a controller: ```
    ```

public function search(\\Avansaber\\RedditApi\\Http\\RedditApiClient $client) { // For app-only reads you can fetch a token via Auth::appOnly and call withToken() // $token = ...; $client-&gt;withToken($token); return response()-&gt;json($client-&gt;search()-&gt;get('php', \['limit' =&gt; 5\])); } ```

- For user-context (vote/reply), obtain a user access token (e.g., Socialite Providers: Reddit, or README’s temporary curl step) and call `$client->withToken($userAccessToken)`.
- CodeIgniter 4

    - Create a service in `app/Config/Services.php` that returns `RedditApiClient` using discovery (same as above), then type-hint it in controllers.
    - Provide env keys: `REDDIT_USER_AGENT`, `REDDIT_CLIENT_ID`, `REDDIT_CLIENT_SECRET`.

Examples

- App-only + Search: `examples/app_only_search.php`
- Me endpoint with existing token: `examples/me.php`
- PKCE OAuth flow with CSRF: `examples/pkce_auth.php`
- Voting on posts/comments: `examples/voting.php`
- Pagination through results: `examples/pagination.php`
- Auto-refresh token handling: `examples/auto_refresh.php`
- Moderation actions: `examples/moderation.php`

Laravel

- See the [Laravel bridge package](https://github.com/avansaber/avansaber-laravel-reddit-api) for first-class Laravel support.

Troubleshooting (403 "whoa there, pardner!")

- Reddit may block requests based on IP/UA policies (common with VPN/DC IPs or generic UAs).
- Use a descriptive UA including your Reddit username, e.g. `macos:avansaber-php-reddit-api:0.1 (by /u/YourRedditUsername)`.
- Run from a residential network; avoid VPN/corporate IPs. Add small delays between calls.
- If still blocked, file a ticket with Reddit and include the block code from the response page.

Security notes

- Treat client secrets and access tokens as sensitive. Use environment variables and do not commit them.
- Rotate secrets if they were exposed during testing.

Contributing

- See CONTRIBUTING.md

Changelog

- See `CHANGELOG.md`. We follow Conventional Commits and tag releases.

Security

- See SECURITY.md

License

- MIT

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance77

Regular maintenance activity

Popularity34

Limited adoption so far

Community17

Small or concentrated contributor base

Maturity33

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

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

Unknown

Total

1

Last Release

125d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/5685bdccb475a5abb647fc1daeb97aff5e44ac72e59f8048ff3772ac4c7ae1e0?d=identicon)[mailnike](/maintainers/mailnike)

---

Top Contributors

[![mailnike](https://avatars.githubusercontent.com/u/22786232?v=4)](https://github.com/mailnike "mailnike (19 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/avansaber-php-reddit-api/health.svg)

```
[![Health](https://phpackages.com/badges/avansaber-php-reddit-api/health.svg)](https://phpackages.com/packages/avansaber-php-reddit-api)
```

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[aporat/store-receipt-validator

PHP receipt validator for Apple App Store and Amazon Appstore

6503.9M9](/packages/aporat-store-receipt-validator)[theodo-group/llphant

LLPhant is a library to help you build Generative AI applications.

1.5k311.5k5](/packages/theodo-group-llphant)[opensearch-project/opensearch-php

PHP Client for OpenSearch

15224.3M65](/packages/opensearch-project-opensearch-php)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6939.5M343](/packages/drupal-core-recommended)[tempest/framework

The PHP framework that gets out of your way.

2.1k23.1k9](/packages/tempest-framework)

PHPackages © 2026

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