PHP SDK
Connect maxclicks to your PHP application: update customer records, send events, trigger prepared emails, and read results through the Public API.
Current API coverage
The SDK groups Public API calls by resource. Use the API reference for request fields, response payloads, and operation semantics, and the examples below for language-specific method calls.
Pagination helpers follow the API traversal limits. An iterator or method named all stops at the traversal cap; use scoped queries when working with larger datasets.
Install
Install the SDK with Composer:
composer require maxclicks/maxclicks-php
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 resource methods to read them or act on prepared work. Manage API keys and CSV imports in the app. See the API reference for request fields and response details.
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',
);
Pass an idempotency key to writes such as records->create, records->upsert, suppressions->batchCreate, suppressions->batchDelete, templates->send, broadcasts->send, and workflows->trigger. See the supported API writes and retry contract before replaying an operation. 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.