1. Go to this page and download the library: Download jcolombo/optmyzr-api-php library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
jcolombo / optmyzr-api-php example snippets
use Jcolombo\OptmyzrApiPhp\Optmyzr;
$optmyzr = Optmyzr::connect(getenv('OPTMYZR_API_TOKEN'));
use Jcolombo\OptmyzrApiPhp\Optmyzr;
$optmyzr = Optmyzr::connect(getenv('OPTMYZR_API_TOKEN'));
// Which operations does this token actually have access to?
// (Also the cheapest call in the API, so a good liveness check.)
if (!$optmyzr->supports('Accounts')) {
throw new RuntimeException('This token has no Accounts access.');
}
// Every ad account the token can see
foreach ($optmyzr->accounts()->fetch() as $id => $account) {
// $id is a stable synthetic identity — safe as a local database key
echo $id, ' ', $account->accountName(), ' (', $account->accountType(), ")\n";
}
// One client's pending work
$acct = $optmyzr->account('123-456-7890', 'AdWords');
$suggestions = $acct->suggestions()->fetch();
echo $suggestions->totalChanges(), " pending changes\n";
foreach ($acct->alerts()->fetch() as $alert) {
echo $alert->alertType(), ': ', $alert->alertDetails(), "\n";
}
// Who applied what, last month
$history = $optmyzr->history()
->between('2026-07-01', '2026-07-31')
->fetch();
foreach ($history->changesByUser() as $email => $changes) {
echo $email, ': ', $changes, " changes\n";
}
// Overdue Blueprint tasks, across every page
$overdue = $optmyzr->blueprintTasks()->overdue()->fetchAll();
echo $overdue->count(), ' overdue across ', $overdue->pagesFetched(), " pages\n";
Optmyzr::disconnect();
$suggestion = $optmyzr->suggestions()->fetch()->first();
$suggestion->naturalKey(); // "google|123-456-7890|Search Terms"
$suggestion->id(); // "optsug_1f3c8a…" stable
$suggestion->fingerprint(); // "9b21ef…" moves on any change
// If real data shows a key is not unique, correct it without a package release:
Configuration::set('identity.keys.activeAlert', ['accountType', 'accountId', 'alertType', 'link', 'alertDetails']);
$alerts = $optmyzr->alerts()->fetch();
if ($alerts->hasCollisions()) {
// Two distinct records share an id — the configured key is missing a field.
print_r($alerts->collisions());
}
// Server-side: the endpoint has an accountId parameter
$optmyzr->metrics()->accountId('123-456-7890');
// Client-side: the Accounts endpoint has no accountId parameter, so where()
// filters in PHP after the rows arrive
$accounts = $optmyzr->accounts()->where('accountId', '123-456-7890');
$accounts->fetch();
print_r($accounts->explain());
// [
// 'endpoint' => 'Accounts',
// 'paginated' => false,
// 'server' => [],
// 'client' => ['accountId = 123-456-7890'],
// 'sort' => null,
// 'take' => null,
// ]
$optmyzr->suggestions()
->filter(fn ($s) => $s->changes() > 5, 'changes > 5')
->sortBy('changes', SortDirection::DESC) // no sort parameter exists in the API
->take(10)
->fetch();
$acct = $optmyzr->account('123-456-7890', 'AdWords');
$acct->metrics()->fetch(); // accountId + accountType server-side
$acct->alerts()->fetch(); // accountId + accountType server-side
$acct->suggestions()->fetch(); // accountId + accountType server-side
$acct->history()->fetch(); // accountId + accountType server-side
$acct->workoutHistory()->fetch(); // accountId server-side, platform matched in PHP
$acct->blueprintTasks()->fetch(); // accountId server-side
$acct->detail(); // the Accounts row for this account, or null
foreach ($optmyzr->metrics()->fetch()->byAccount() as $key => $group) {
// $key is 'google|123-456-7890'; $group is a full sub-collection
echo $key, ': ', $group->averageQualityScore(), "\n";
}
$tasks = $optmyzr->blueprintTasks()->status('Open')->fetchAll();
echo $tasks->count(), ' tasks across ', $tasks->pagesFetched(), " pages\n";
// Resume a partial sweep
$optmyzr->blueprintTasks()->page(4)->fetchAll();
$metrics = $optmyzr->metrics()->fetch();
$metrics->entryCount(); // raw rows the API returned — exactly 1000 is suspicious
$metrics->totalCount(); // distinct records held
$previous = $store->loadSnapshot('optmyzr.suggestions'); // [id => fingerprint]
$current = $optmyzr->suggestions()->fetch();
$diff = $current->diff($previous);
$store->insert($diff->added()); // identities never seen before
$store->update($diff->changed()); // same identity, different contents
$store->retire($diff->removed()); // identities absent from this fetch
// $diff->unchanged() is skipped entirely — no write, no churn
$store->saveSnapshot('optmyzr.suggestions', $diff->snapshot());
try {
$account->save();
} catch (ReadOnlyResourceException $e) {
// "Optmyzr Account is read-only: save() is not available. The Optmyzr User
// Data API v1 exposes GET operations only — it has no write endpoints."
}
$account->accountType(); // 'AdWords' — exactly what the API sent
$account->accountTypeEnum(); // AccountType::ADWORDS, or null if unrecognised
$account->platform(); // 'google' — normalised, rename-proof
Configuration::set('enabled.logging', true);
Configuration::set('path.logs', '/var/log/myapp');
Configuration::set('error.handlers.fatal', ['log']); // drop the default 'echo'
Configuration::set('error.triggerPhpErrors', true); // escalate to PHP errors
use Jcolombo\OptmyzrApiPhp\Configuration;
Configuration::load('/path/to/optmyzrapi.config.json'); // merge a file
Configuration::overload('/path/to/dir'); // dir/optmyzrapi.config.json, ignored if absent
Configuration::set('devMode', true); // one value
use Jcolombo\OptmyzrApiPhp\Entity\EntityMap;
class MyAccount extends \Jcolombo\OptmyzrApiPhp\Entity\Resource\Account
{
public function customerNumber(): ?string
{
return $this->accountName() === null ? null : substr($this->accountName(), 0, 4);
}
}
EntityMap::overload('account', MyAccount::class);
// The facade now hydrates your subclass
$optmyzr->accounts()->fetch()->first()->customerNumber();