Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Api Library Laravel Package

mautic/api-library

A PHP client library for the Mautic API, providing simple, framework-agnostic access to contacts, segments, campaigns, emails, forms, and other REST endpoints. Handles authentication and request/response parsing so you can integrate Mautic marketing automation into your PHP applications.

Deep Wiki
Context7

Getting Started

Install with Composer and note that the package is framework-agnostic (no Laravel service provider ships with it — you'll wire it yourself):

composer require mautic/api-library

Read these first, in order:

  1. README.md — the auth flow example is the only official tutorial; everything else is discoverable from code.
  2. src/Auth/ApiAuth.php — factory for auth strategies (OAuth2, OAuth1a, BasicAuth).
  3. src/MauticApi.php — the context factory (getContext()).
  4. src/Api/*.php — one class per endpoint; the methods defined there are your real API surface, not the docs.

Configure it in config/services.php:

'mautic' => [
    'base_url'      => env('MAUTIC_BASE_URL'),        // e.g. https://marketing.example.com
    'client_key'    => env('MAUTIC_CLIENT_KEY'),
    'client_secret' => env('MAUTIC_CLIENT_SECRET'),
    'callback'      => env('MAUTIC_CALLBACK_URL'),
    'version'       => env('MAUTIC_AUTH_VERSION', 'OAuth2'),
],

Your first call (basic auth, server-to-server):

use Mautic\Auth\ApiAuth;
use Mautic\MauticApi;

$auth = (new ApiAuth())->newAuth([
    'baseUrl'  => config('services.mautic.base_url'),
    'userName' => config('services.mautic.user_name'),
    'password' => config('services.mautic.password'),
    'version'  => 'BasicAuth',
]);

$contacts = (new MauticApi())->getContext(
    'contacts',
    $auth,
    config('services.mautic.base_url')
);

$response = $contacts->create([
    'email'     => '[email protected]',
    'firstname' => 'Jane',
    'lastname'  => 'Doe',
]);

$id = $response['contact']['id'] ?? null;

First real use case to build: a queued job that pushes a lead into Mautic when a user registers, tagging the source and adding them to a segment. That exercises auth, create, getContactIdByEmail (for idempotency), and a second context (segments) in one flow, which is the shape 80% of Mautic integrations take.

Implementation Patterns

Bind a singleton, don't rebuild auth per call. The $auth object holds your token state; recreating it every request means re-reading and re-validating tokens unnecessarily.

// AppServiceProvider::register()
$this->app->singleton(\Mautic\Auth\AbstractAuth::class, function () {
    return (new \Mautic\Auth\ApiAuth())->newAuth([
        'baseUrl'      => config('services.mautic.base_url'),
        'clientKey'    => config('services.mautic.client_key'),
        'clientSecret' => config('services.mautic.client_secret'),
        'callback'     => config('services.mautic.callback'),
        'version'      => 'OAuth2',
    ], [
        // hydrate from your token store, not $_SESSION
        'access_token'  => Cache::get('mautic.access_token'),
        'refresh_token' => Cache::get('mautic.refresh_token'),
        'expires_in'    => Cache::get('mautic.expires_in'),
        'token_type'    => 'Bearer',
    ]);
});

Store OAuth tokens in a table, not the session. The library's examples use session_start() + $_SESSION, which breaks in queue workers and stateless APIs. Persist access_token, refresh_token, expires_in, and an updated_at, then hydrate via the second argument to newAuth().

Wrap contexts in your own typed client. The raw return is an array. A thin wrapper pays for itself immediately:

class MauticContacts
{
    public function __construct(private \Mautic\Api\Contacts $api) {}

    public function upsert(array $payload): string
    {
        $existing = $this->api->getContactIdByEmail($payload['email']);

        if ($existing) {
            $this->api->edit($existing, $payload);   // PATCH — partial update
            return $existing;
        }

        return $this->api->create($payload)['contact']['id'];
    }
}

Know update vs edit. update($id, $data) is a PUT — omitted fields get nulled. edit($id, $data) is a PATCH — only sent fields change. Almost every integration wants edit(); reaching for update() to change a single tag is a classic data-loss bug.

Use the search-string API, not SQL. List methods take Mautic's own filter syntax:

$api->getList('where[1][col]=email and where[1][expr]=eq and where[1][val][email protected]', 0, 30);

Build these with a tiny helper rather than hand-concatenating — the where[n] indices are easy to get wrong and Mautic silently returns all rows if parsing fails.

Paginate explicitly. getList($search, $start, $limit) defaults are small; loop on start until total is exhausted. Note that list responses return entities keyed by ID (['contacts' => ['42' => [...]]]), not as a plain array — use array_values() before collect().

Prefer queued jobs with retry/backoff. Mautic's API is the slowest hop in most registration flows. Dispatch a job, catch transport exceptions, retry 3× with backoff. Don't let a marketing API outage fail your checkout.

One context per class. segments is where you do membership; contacts is where you do identity; tags is where you do classification. Mixing contexts in one service makes tests brittle and hides the N+1 requests. Wrap MauticApi::getContext('segments', $auth, $url) in its own injected service.

Gotchas and Tips

OAuth2 assumes $_SESSION. Mautic\Auth\OAuth reads and writes tokens to $_SESSION and calls session_start() internally. In queue workers, API middleware, or Octane this is a footgun. Either pass tokens explicitly in the newAuth() second argument (and call getAccessTokenData() after validateAccessToken() to persist them yourself), or prefer BasicAuth for machine-to-machine. Verify Basic Auth is still enabled on your Mautic version — it has been deprecated/removed across some releases.

Token refresh is not automatic on every call. Call $auth->validateAccessToken() before your API batch, then check $auth->accessTokenUpdated() and persist the new tokens. If you skip this, requests start returning 401 after ~1 hour and the failure is silent in list responses.

baseUrl is the site root, not the API path. Pass https://marketing.example.com; the library appends /api/... itself. Do not pass .../api. Trailing slashes are tolerated inconsistently across versions — strip them.

Errors are usually in the array, not thrown. The common failure path returns something like ['errors' => [['code' => 404, 'message' => '...']]]. Check isset($response['errors']) in a single guard method; only transport-level problems surface as Guzzle exceptions. Wrapping everything in try/catch and ignoring the array shape will eat your 400s silently.

IDs are strings. $response['contact']['id'] comes back as "42". Comparing with === against an int, or using it as an array key downstream, bites. Cast at the boundary.

getContactIdByEmail is a lookup, not an upsert. Two concurrent jobs for the same email will both miss and both create(), producing duplicate contacts. If Mautic allows duplicate emails on your install, either serialize per-email (Redis lock) or rely on Mautic's dedupe and re-query after create.

Custom fields use the alias, not the label. { "custom_field_name": "value" } — where custom_field_name is the alias set in Mautic's field manager. Creating with the label silently drops the value.

Batch methods are inconsistent across contexts. Some Mautic\Api\* classes expose batch* methods, others don't, and the underlying endpoints differ (/contacts/batch/new vs. looping). Read the context class before assuming batch() exists; for anything over a few hundred records, chunk manually at 100–200 and expect a rate limit you'll need to handle.

Inject a configured Guzzle client for timeouts and proxies. The Api base class exposes setClient(). Without it you get default Guzzle timeouts (~0) and no proxy/retry middleware — the single most common production incident with this package:

$api->setClient(new \GuzzleHttp\Client([
    'timeout'         => 10,
    'connect_timeout' => 3,
    'retry'           => 2,
]));

Use getRequest()/getResponse() when debugging. The Api instance retains the last PSR-7 request/response, which is far faster than enabling Guzzle logging to see exactly what filter string Mautic received (and how it mangled it).

Test by faking the client, not the network. Since every context ultimately routes through Guzzle, bind a MockHandler-backed client in tests and assert on the request body/URI. There's no Laravel Http::fake() equivalent — you have to intercept at the Guzzle layer.

Extension point: Mautic\Auth\ApiAuth::newAuth() and Mautic\MauticApi::getContext() are both plain factories. Subclass MauticApi to add contexts not shipped in the library (or to apply the Guzzle client + token hydration globally) — that's cleaner than wrapping every call site.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle