PHP SDK
Connect maxclicks to your PHP application: update customer records, send events, trigger prepared emails, and read results through the Public API.
The Packagist release is pending. Configure the source repository below, or follow the HTTP quickstart to connect without a client library.
Current API coverage
This guide documents the existing source client interface. The current REST API also exposes event readiness, event identity changes, record deletion status, and workflow history cursors that this client does not yet wrap. Event receipts and deletion results have changed; older static models may omit fields or expect the former { id, deleted } shape.
Use direct HTTP for those operations and verify the current coverage table before adopting a wrapper. Auto-pagination still stops at the API's traversal cap; an iterator or method named all does not guarantee a full workspace export.
Install
The Packagist release is not published yet. Until it is, add the source repository to composer.json and require it from there:
composer config repositories.maxclicks vcs https://github.com/maxclicks-ai/maxclicks-php
composer require maxclicks/maxclicks-php:dev-main
The SDK auto-discovers any installed PSR-18 client and PSR-17 factories via php-http/discovery. If you have none, install one:
composer require guzzlehttp/guzzle nyholm/psr7
Client
Pass an API key, or call new Maxclicks() to read MAXCLICKS_API_KEY. The default base URL is https://api.maxclicks.ai/v1.
use Maxclicks\Maxclicks;
$mc = new Maxclicks('max_...');
$me = $mc->me();
echo $me->user['email'];
Resource groups are properties on the client ($mc->records, $mc->events, $mc->templates, and so on), plus a top-level me() method.
$contact = $mc->records->upsert('students', [
'email' => '[email protected]',
'firstName' => 'Ada',
'favoriteColor' => 'blue', // custom attribute
]);
echo $contact->id();
echo $contact['favoriteColor']; // unknown keys preserved
Record and Event models are flat and keep the full payload. Read base fields with typed accessors (id(), email(), tags()) and any key with array access, get('key'), or toArray().
Template, broadcast, segment and webhook ids are opaque strings the API returns; do not assume a uuid or a fixed prefix. Schemas take either a slug ('students') or an id, and workflows->trigger() takes the reference id of the workflow's incoming-webhook step.
Resource surface
The live v1 surface is schemas, attributes, records, suppressions, events, domains, senders, topics, segments, broadcasts, templates, webhooks, workflows, forms and emails, plus me() on the client.
Create schemas, attributes, domains, senders, topics, templates and broadcasts in the maxclicks app, then use the supported API operations to read them or act on prepared work. Manage API keys and CSV imports in the app too. The client's method list and the current API reference define what can be changed or triggered from code; use direct HTTP when a newer operation is not wrapped by this client.
Build the entity once in the app, then drive it from PHP: $mc->broadcasts->send(), $mc->templates->send(), $mc->workflows->trigger(), and the whole read surface.
Configuration
Pass a bare API key, an options array, or a Maxclicks\Config.
| Option | Default | Notes |
|---|---|---|
apiKey | MAXCLICKS_API_KEY env | Bearer token, sent on all but public endpoints. |
baseUrl | https://api.maxclicks.ai/v1 | Use Config::STAGE_BASE_URL for staging. |
timeout | 60.0 seconds | Honored by clients that support it. |
maxRetries | 2 | Retries on 429, plus 5xx and transport/timeout errors for reads and keyed writes. |
defaultHeaders | [] | Merged into every request. |
onWarning | null | fn(array $warnings, string $method, string $path). |
use Maxclicks\Config;
use Maxclicks\Maxclicks;
$mc = new Maxclicks([
'apiKey' => 'max_...',
'baseUrl' => Config::STAGE_BASE_URL,
'timeout' => 60.0,
'maxRetries' => 2,
// Inject your own PSR-18 client / PSR-17 factories:
// 'httpClient' => $psr18Client,
// 'requestFactory' => $psr17RequestFactory,
// 'streamFactory' => $psr17StreamFactory,
]);
Pagination
List methods return a Maxclicks\Pagination\Page. It is both the current page and a lazy iterator over every page.
$page = $mc->records->list('students', limit: 100);
foreach ($page->data as $record) { /* this page */ }
$page->pagination->totalCount;
foreach ($mc->records->list('students') as $record) {
echo $record->id(); // fetches next pages on demand
}
$all = $mc->records->list('students')->all();
Auto-pagination stops when hasMore is false or the offset would exceed 10000. events->list() uses cursor pagination and returns a Maxclicks\Pagination\CursorPage, which follows nextCursor automatically.
Idempotency
The writes that support it accept an optional trailing idempotencyKey. Replaying a key returns the original result instead of acting twice.
$mc->records->create(
'students',
['email' => '[email protected]', 'firstName' => 'Ada'],
idempotencyKey: 'signup-2026-07-18-ada',
);
This source client exposes a key on seven operations: records->create, records->upsert, suppressions->batchCreate, suppressions->batchDelete, templates->send, broadcasts->send, and workflows->trigger. Other methods do not expose a key argument. Use direct HTTP for the additional supported API writes; do not assume every update or deletion is safe to replay. Event fires are deliberately not key-aware: dedupe them with your own eventId. See Idempotency for the wire-level contract.
Errors
Every failure throws a typed exception extending Maxclicks\Exceptions\MaxclicksException. API errors carry status, code, type, issues, response headers, and the raw body.
use Maxclicks\Exceptions\MaxclicksApiException;
use Maxclicks\Exceptions\MaxclicksNotFoundException;
use Maxclicks\Exceptions\MaxclicksRateLimitException;
try {
$mc->records->get('students', 'missing-id');
} catch (MaxclicksNotFoundException $e) {
// 404
} catch (MaxclicksRateLimitException $e) {
sleep((int) ceil($e->retryAfterSeconds ?? 1.0));
} catch (MaxclicksApiException $e) {
error_log("{$e->status} {$e->code}: {$e->getMessage()}");
}
| Exception | When |
|---|---|
MaxclicksBadRequestException | 400 |
MaxclicksAuthenticationException | 401 |
MaxclicksPermissionException | 403 |
MaxclicksNotFoundException | 404 |
MaxclicksConflictException | 409 |
MaxclicksUnprocessableEntityException | 422 |
MaxclicksRateLimitException | 429 (has retryAfterSeconds) |
MaxclicksServerException | 5xx |
MaxclicksConnectionException | transport failure (DNS, TCP, TLS) |
MaxclicksTimeoutException | request timed out |
MaxclicksConfigurationException | programmer error (missing API key) |
Retries are limited to what is safe to repeat. Reads retry on 429, 5xx, and transport or timeout errors. A write retries on those only when it carries an idempotencyKey, so a keyless send that times out is thrown to you rather than sent again. The client also retries 429 responses according to its configured retry budget. Backoff is exponential with full jitter (base 0.5s, cap 8s), and a Retry-After header on a 429 sets the delay floor.
Some 422s are validation gates, not batch results. Sending a not-ready broadcast throws MaxclicksUnprocessableEntityException with $e->issues listing the problems.
use Maxclicks\Exceptions\MaxclicksUnprocessableEntityException;
try {
$mc->broadcasts->send('cmb3k1x2p0000s601wxyz9876');
} catch (MaxclicksUnprocessableEntityException $e) {
foreach ($e->issues ?? [] as $issue) {
error_log($issue);
}
}
events->fireBatch() is not an exception case. It returns a BatchFireResult even when the API returns 422 for partial failure. Check hasFailures() and iterate results.
Warnings
Successful responses may include warnings, in the body's warnings array or in the maxclicks-Warning-Message response header. Provide an onWarning callback to receive them, which is the only way to see warnings from methods like templates->send() and workflows->trigger() that return nothing to the caller. Paginated results also expose $page->warnings.
Laravel
The package ships a service provider and a Maxclicks facade, auto-discovered by Laravel. Set your key in the environment:
MAXCLICKS_API_KEY=max_...
# optional:
MAXCLICKS_BASE_URL=https://api.maxclicks.ai/v1
MAXCLICKS_TIMEOUT=60
MAXCLICKS_MAX_RETRIES=2
Publish the config file if you want to edit it:
php artisan vendor:publish --tag=maxclicks-config
The facade forwards top-level calls (like me()) to the shared client. Resource groups are properties, so reach them through the resolved instance.
use Maxclicks\Laravel\Maxclicks;
$me = Maxclicks::me();
$contact = app(\Maxclicks\Maxclicks::class)
->records->upsert('students', ['email' => '[email protected]']);
// or inject the client
public function __construct(private \Maxclicks\Maxclicks $maxclicks) {}
The client also falls back to config/services.php under the maxclicks key (['maxclicks' => ['key' => env('MAXCLICKS_API_KEY')]]).
Rate limits
The Public API allows about 100 read requests per second and 25 write requests per second per key. The SDK retries 429 responses automatically, honoring Retry-After.
Retry outcomes
A repeated write can return a recorded result, including an error. If the API reports idempotency_outcome_unknown, inspect the affected resource or run history before creating a new operation. Keep the original key and payload while you reconcile. See the idempotency guide for supported operations and retention.