cleverreach / php-sdk
Official PHP SDK for the CleverReach REST API
Requires
- php: ^8.2
- php-http/discovery: ^1.19.2
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
Requires (Dev)
- nyholm/psr7: ^1.8
- php-http/client-common: ^2.7.1
- php-http/mock-client: ^1.6
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
- squizlabs/php_codesniffer: ^3.10 || ^4.0
README
A lightweight PHP SDK for the CleverReach REST API. Built on PSR-18 / PSR-17 interfaces
Table of Contents
- Requirements
- Installation
- Quick Start
- OAuth 2.0 Flow
- Features
- Typed Service API
- Raw API Access
- Error Handling
- Advanced: Bring Your Own HTTP Client
- Development
- License
Requirements
| Requirement | Version |
|---|---|
| PHP | 8.2 or higher |
| Composer | 2.x |
You also need one PSR-18-compatible HTTP client in your project. Popular choices:
# Guzzle composer require guzzlehttp/guzzle # Symfony HTTP Client composer require symfony/http-client nyholm/psr7
The SDK auto-detects the available client via php-http/discovery – no additional configuration needed.
Installation
composer require cleverreach/sdk-php
Quick Start
Get up and running in under a minute:
<?php require __DIR__ . '/vendor/autoload.php'; use CleverReach\SDK\CleverReachClient; // 1. Create a client with your API token $client = new CleverReachClient('YOUR_API_TOKEN'); // 2. Fetch all groups using the typed service $groups = $client->groups()->all(); foreach ($groups as $group) { echo $group->name . PHP_EOL; } // Alternatively, via raw request (returns arrays): // $groupsArray = $client->request('GET', 'groups'); // echo $groupsArray[0]['name'];
That's it. The SDK handles authentication, JSON encoding/decoding, and error mapping automatically.
Get your API Token via the "Test process now" button in the CleverReach backend under My Account → Interfaces → REST API OR use the built-in OAuth functions.
OAuth 2.0 Flow
If you are building an app for multiple CleverReach customers, you need to use OAuth 2.0 instead of a rigid, static API token. The SDK provides an OAuthHelper to manage the complete authorization flow including strict CSRF validation (state checking) and automatic token-refresh routines.
1. Generating Login URL
use CleverReach\SDK\Auth\OAuthHelper; session_start(); $oauthHelper = new OAuthHelper('YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET', 'https://your-domain.com/callback'); // Generate secure state against CSRF $state = bin2hex(random_bytes(16)); $_SESSION['oauth_state'] = $state; $url = $oauthHelper->getAuthorizationUrl($state, ['receivers.read', 'groups.read']); header('Location: ' . $url); exit;
2. Handling the Callback
When CleverReach redirects the user back to your redirect_uri (e.g., callback), exchange the code for fully managed tokens. The SDK saves them per default on disk (FileTokenStorage), but you can inject a custom TokenStorageInterface to use Redis or Eloquent.
Pro Tip: The SDK does NOT map tokens to users automatically (to remain agnostic). In a multi-user environment, implement the
TokenStorageInterfaceto link the storedTokensto the currently logged-in user in your database.
use CleverReach\SDK\Auth\Exceptions\CleverReachAuthException; use CleverReach\SDK\Auth\OAuthHelper; session_start(); // Make sure to construct the helper with exactly your credentials $oauthHelper = new OAuthHelper('YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET', 'https://your-domain.com/callback'); try { $expectedState = $_SESSION['oauth_state'] ?? ''; $receivedState = $_GET['state'] ?? ''; $code = $_GET['code'] ?? ''; // Exchanges the code and validates the returned state against the session value $tokens = $oauthHelper->exchangeCodeForToken($code, $receivedState, $expectedState); echo "Login success! Tokens cached."; } catch (CleverReachAuthException $e) { die("Authorization failed: " . $e->getMessage()); }
Custom Token Storage (Database/Redis)
For multi-tenant applications, you should write your own storage adapter by implementing the TokenStorageInterface. This allows you to persistently store and retrieve the tokens based on your system's user ID.
use CleverReach\SDK\Auth\Storage\TokenStorageInterface; use CleverReach\SDK\Auth\Tokens; class MyDatabaseTokenStorage implements TokenStorageInterface { public function __construct(private int $userId) {} public function get(): ?Tokens { // SELECT * FROM oauth_tokens WHERE user_id = $this->userId // if found, return Tokens::fromArray($dbData); // else return null; } public function set(Tokens $tokens): void { // UPDATE/INSERT INTO oauth_tokens WHERE user_id = $this->userId // You can also access Scopes: $tokens->hasScope('receivers.read') } public function delete(): void { // DELETE FROM oauth_tokens WHERE user_id = $this->userId } } // Pass it to the Helper during setup $storage = new MyDatabaseTokenStorage($_SESSION['user_id']); $oauthHelper = new OAuthHelper('CLIENT_ID', 'SECRET', 'CALLBACK', $storage);
3. API Requests with OAuth
Hook the OAuthHelper into your client. It acts as a TokenProvider and will autonomously fetch or refresh access tokens prior to any endpoint requests.
use CleverReach\SDK\CleverReachClient; $client = new CleverReachClient(); // Leave token empty $client->setTokenProvider($oauthHelper); // SDK handles adding Bearer token. // If the token is expired, SDK will automatically refresh & retry the API call. $groups = $client->groups()->all();
Revoking Tokens (Logout)
If a user uninstalls your app or logs out, you should actively revoke the token to invalidate it on the CleverReach servers:
// Deletes the token locally and on the server $oauthHelper->revokeToken($tokens->getAccessToken());
Features
- Zero boilerplate – one class, one method
- PSR-18 / PSR-17 – works with Guzzle, Symfony HTTP Client, or any compliant client
- Auto-discovery – no manual HTTP client setup required
- Typed service API – key endpoints covered by dedicated service methods with full IDE autocompletion and type-safe enums
- Raw access – every CleverReach REST endpoint reachable with
request()
Typed Service API
The SDK provides typed service classes for the most common endpoints. These are the recommended way to interact with CleverReach – your IDE will autocomplete parameters and the compiler will catch type errors.
Groups
use CleverReach\SDK\CleverReachClient; use CleverReach\SDK\Enum\GroupSortField; use CleverReach\SDK\Enum\SortOrder; $client = new CleverReachClient('YOUR_API_TOKEN'); // Fetch all groups, sorted by last change descending $groups = $client->groups()->all( order: GroupSortField::Changed, direction: SortOrder::Descending ); foreach ($groups as $group) { echo $group->id . ': ' . $group->name . PHP_EOL; } // Fetch a single group by ID $group = $client->groups()->get(123); echo $group->name;
Receivers (via group)
use CleverReach\SDK\Enum\ReceiverSortField; use CleverReach\SDK\Enum\ReceiverType; use CleverReach\SDK\Enum\SortOrder; $client = new CleverReachClient('YOUR_API_TOKEN'); // List active receivers in a group, sorted by email $receivers = $client->groups()->getReceivers( groupId: 123, page: 0, pageSize: 100, type: ReceiverType::Active, orderBy: ReceiverSortField::Email, orderDirection: SortOrder::Ascending ); foreach ($receivers as $receiver) { echo $receiver->email . PHP_EOL; } // Filter by specific emails or IDs $receivers = $client->groups()->getReceivers( groupId: 123, emailList: ['jane@example.com', 'bob@example.com'] );
Receiver lookup
// By numeric ID $receiver = $client->receivers()->get(381940); // By email address $receiver = $client->receivers()->get('jane@example.com'); echo $receiver->email . ' – active: ' . ($receiver->active ? 'yes' : 'no');
Error Handling
All service methods and request() throw exceptions from CleverReach\SDK\Exception\:
| Exception | When |
|---|---|
AuthenticationException |
401 – invalid or expired API token |
ValidationException |
400 – bad request (invalid payload or parameters) |
ResourceNotFoundException |
404 – resource does not exist (e.g. invalid group ID) |
RateLimitExceededException |
429 – too many requests |
MissingDependencyException |
No PSR-18 HTTP client found at construction time |
CleverReachException |
Any other API error (4xx/5xx), network failure, or invalid JSON |
All specific exceptions extend CleverReachException, so you can catch all errors with a single catch block, or handle specific cases:
use CleverReach\SDK\Exception\AuthenticationException; use CleverReach\SDK\Exception\RateLimitExceededException; use CleverReach\SDK\Exception\CleverReachException; try { $groups = $client->groups()->all(); } catch (AuthenticationException $e) { // Token invalid – refresh and retry echo 'Auth error: ' . $e->getMessage(); } catch (RateLimitExceededException $e) { // Too many requests - sleep and retry sleep(60); } catch (CleverReachException $e) { // Network error, API error, etc. echo 'API error ' . $e->statusCode() . ': ' . $e->getMessage(); echo 'Raw response body: ' . $e->responseBody(); }
Raw API Access
All requests go through the single request() method:
request(string $method, string $endpoint, array $query = [], ?array $json = null): array
| Parameter | Type | Description |
|---|---|---|
$method |
string |
HTTP verb: GET, POST, PUT, DELETE |
$endpoint |
string |
Path relative to https://rest.cleverreach.com/v3/ |
$query |
array |
URL query parameters (null values are ignored) |
$json |
array|null |
Request body, JSON-encoded automatically |
Returns array – either an associative array (single object) or a list of associative arrays.
Example: Custom Endpoint
If an endpoint is not yet covered by the typed services, you can easily call it directly:
$response = $client->request( 'POST', 'some/custom/endpoint', ['query_param' => 'value'], ['json_key' => 'json_value'] );
Full API Reference
The examples above cover the most common use cases. The CleverReach REST API offers many more endpoints – reports, forms, attributes, filters, orders, events, and more.
→ CleverReach REST API Documentation Full endpoint reference, request/response schemas, and interactive Swagger UI.
Advanced: Bring Your Own HTTP Client
By default the SDK uses php-http/discovery to find a PSR-18 client automatically.
You can inject your own implementations – ideal for DI containers or testing:
use CleverReach\SDK\CleverReachClient; $client = new CleverReachClient( apiToken: 'YOUR_API_TOKEN', baseUri: 'https://rest.cleverreach.com/v3/', // optional, this is the default httpClient: $myPsr18Client, requestFactory: $myPsr17RequestFactory, streamFactory: $myPsr17StreamFactory, );
Example: Guzzle with explicit setup
use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Psr7\HttpFactory; use CleverReach\SDK\CleverReachClient; $factory = new HttpFactory(); $client = new CleverReachClient( apiToken: 'YOUR_API_TOKEN', httpClient: new GuzzleClient(), requestFactory: $factory, streamFactory: $factory, );
Development
# Install dependencies composer install # Syntax check composer lint # Code style check composer cs-check # Auto-fix code style composer cs-fix # Run test suite composer test
License
This project is licensed under the MIT License. See the LICENSE file for details.