Skip to content

jonth93/acsmon-php-client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ACS Monitor — PHP API Client

Open-source PHP client library for the ACS Monitor REST API — the network and infrastructure monitoring platform from Anglia Computer Solutions.

PSR-4 client class for PHP 8.1+. Drop the src/ folder into another project's composer.json (or use this folder as a standalone composer package) and you've got typed access to the API. Composer pulls in a single HTTP-client dependency on first install.

Licensing: this PHP client is open source under the MIT licence. The ACS Monitor application it talks to is licensed commercial software with a free tier for installations up to 100 monitored resources — see acsmon.com for tiers and pricing.


Table of contents


Requirements

Requirement Version
PHP 8.1 or newer (uses readonly + first-class enums)
Composer 2.x
HTTP client pulled in automatically as a dependency
Network outbound HTTPS to your ACS Monitor instance

The client itself has no other dependencies and works standalone in plain CLI scripts, web frameworks, queue workers, or libraries.

Installation

As a project under api-clients/php

cd api-clients/php
composer install

Vendored into another project

Copy src/AcsMonitorClient.php into your project (under AcsMon\AcsMonitorClient) and add the HTTP client to your own composer.json:

composer require guzzlehttp/guzzle:^7.0

Then add to your autoload.psr-4:

{
    "autoload": {
        "psr-4": { "AcsMon\\": "path/to/src/" }
    }
}

Run composer dump-autoload and you're done.

Quick start

export ACSMON_BASE_URL=https://monitoring.example.com
export [email protected]
export ACSMON_PASSWORD='your-strong-password'

php examples/list-devices.php
php examples/poll-device.php 42

If you'd rather use a long-lived token (recommended for production integrations) skip the email/password vars and set ACSMON_TOKEN instead. The client will use it directly.

Configuration

Constructor signature:

public function __construct(
    string $baseUrl,
    ?string $token = null,
    array $guzzleOptions = []
)
Param Type Description
$baseUrl string e.g. https://monitoring.example.com (no trailing slash needed)
$token ?string Optional bearer token; if null, you must call login() before any other method
$guzzleOptions array Merged into the underlying HTTP client options (timeout, proxy, TLS verify, custom CA bundle, etc.)

The constructor does not perform any network I/O — instantiating the client is safe in any context including service-container bindings.

Authentication

Login flow

$api = new AcsMonitorClient('https://monitoring.example.com');
$token = $api->login('[email protected]', 's3cret');
// $token is also stored on the client instance

The token persists until either of these happens:

  • you explicitly call $api->logout() (revokes the token server-side)
  • the user account is deleted

Re-using the same token across processes is fine — store it in your secret manager and skip the login step.

Using an existing token

$api = new AcsMonitorClient('https://monitoring.example.com', '1|abc...');
// no login() call needed

Inspecting / replacing the token

$api->token();        // returns ?string
$api->setToken($t);   // replace the in-memory token
$api->setToken(null); // forget the token (useful before re-login)

Method reference

Every method below maps 1:1 to an API endpoint. Names follow the endpoint structure where reasonable.

Auth

Signature HTTP
login(string $email, string $password): string POST /auth/login — returns the bearer token
logout(): void POST /auth/logout — revokes the token
token(): ?string local accessor
setToken(?string $token): void local mutator

Devices

Signature HTTP
devices(array $query = []): iterable GET /devices — yields each row, paginated transparently
device(int $id): array GET /devices/{id}
pollDevice(int $id): array POST /devices/{id}/poll
deviceMetrics(int $id, array $query = []): array GET /devices/{id}/metrics

Common $query keys: per_page, page, search, sort, filter[status], filter[group], filter[snmp_version], filter[is_active].

Monitors

Signature HTTP
monitors(array $query = []): iterable GET /monitors — yields each row
monitor(int $id): array GET /monitors/{id}
createMonitor(array $payload): array POST /monitors
checkMonitor(int $id): array POST /monitors/{id}/check
monitorResults(int $id, array $query = []): array GET /monitors/{id}/results
monitorUptime(int $id, array $query = []): array GET /monitors/{id}/uptime

Alerts

Signature HTTP
alertEvents(array $query = []): iterable GET /alert-events
acknowledgeAlertEvent(int $id, ?string $note = null): array POST /alert-events/{id}/acknowledge
resolveAlertEvent(int $id, ?string $note = null): array POST /alert-events/{id}/resolve

System

Signature HTTP
systemHealth(): array GET /system/health

Returned data shapes

All single-resource methods return a array<string,mixed> decoded straight from the JSON response. Iterator methods yield one such array per row.

Example device row:

[
    'id' => 42,
    'name' => 'core-sw1',
    'ip_address' => '10.0.0.4',
    'snmp_version' => '2c',
    'community_string' => 'public',
    'port' => 161,
    'status' => 'up',
    'snmp_enabled' => true,
    'is_active' => true,
    'last_polled_at' => '2026-04-26T14:32:01+00:00',
    'group' => ['id' => 3, 'name' => 'Core Network'],
    // ...
]

Example monitor row:

[
    'id' => 17,
    'name' => 'Public website',
    'type' => 'https',
    'host' => 'example.com',
    'port' => 443,
    'status' => 'up',
    'response_time_ms' => 412,
    'check_interval_seconds' => 60,
    'last_checked_at' => '2026-04-26T14:31:57+00:00',
    'uptime_percent_7d' => 99.97,
    'config' => ['method' => 'GET', 'path' => '/', 'expected_status' => 200],
]

Pagination

Iterator methods (devices(), monitors(), alertEvents()) yield one row at a time and follow next_page_url automatically. You don't need to track the page counter yourself:

foreach ($api->devices() as $device) {
    // every device, across all pages
}

Want to limit how much you pull? Use the standard per_page parameter (server caps at 100 per request) and break out of the loop:

$collected = [];
foreach ($api->devices(['per_page' => 50, 'sort' => '-last_polled_at']) as $device) {
    $collected[] = $device;
    if (count($collected) >= 100) break;   // first 100 most-recently-polled
}

Filtering, sorting, searching

Every list endpoint accepts the same query syntax:

Param Example Purpose
search search=router substring match across name/IP
filter[field] filter[status]=down exact match
filter[field] filter[snmp_version]=3 exact match
sort sort=name / sort=-last_polled_at column name; - for descending
per_page per_page=50 rows per page (max 100)
page page=2 only set manually if you're NOT iterating

Pass them as a normal PHP associative array:

$query = [
    'filter[status]' => 'down',
    'filter[snmp_version]' => '2c',
    'sort' => '-last_polled_at',
    'per_page' => 100,
];
foreach ($api->devices($query) as $device) {
    // …
}

Working with monitors

Creating a service monitor

$monitor = $api->createMonitor([
    'name' => 'Public site (HTTPS)',
    'type' => 'https',
    'host' => 'example.com',
    'port' => 443,
    'check_interval_seconds' => 60,
    'timeout_ms' => 10000,
    'is_active' => true,
    'config' => [
        'method' => 'GET',
        'path' => '/health',
        'expected_status' => 200,
        'follow_redirects' => true,
        'ssl_verify' => true,
        'allow_self_signed' => false,
        'expected_body_regex' => '"status":"ok"',
    ],
]);

Triggering an immediate check

$result = $api->checkMonitor($monitor['id']);
echo $result['status'];           // 'up' | 'down' | 'degraded'
echo $result['response_time_ms']; // ms

Reading recent results

$results = $api->monitorResults($monitor['id'], ['per_page' => 100]);
foreach ($results['data'] ?? [] as $check) {
    echo "{$check['checked_at']}: {$check['status']} ({$check['response_time_ms']}ms)\n";
}

Uptime stats

$uptime = $api->monitorUptime($monitor['id'], ['days' => 30]);
echo "{$uptime['uptime_percent']}% over {$uptime['period_days']}d\n";

Supported monitor types

ping, tcp, http, https, ssl, ssh, smtp, ftp, pop3, imap, dns, mysql, snmp. Each has its own config schema — see the main repository's CLAUDE.md for the full spec.

Working with alerts

Listing open critical alerts

$query = ['filter[status]' => 'open', 'filter[severity]' => 'critical'];
foreach ($api->alertEvents($query) as $event) {
    printf("[%s] %s — %s\n",
        $event['severity'],
        $event['triggered_at'],
        $event['device']['name'] ?? $event['monitor']['name'] ?? 'Unknown',
    );
}

Acknowledging an alert

$api->acknowledgeAlertEvent(123, 'Investigating in INC-9421');

The optional note is recorded on the event timeline and visible in the UI.

Resolving an alert

$api->resolveAlertEvent(123, 'Patched and confirmed up');

Error handling

Anything non-2xx becomes a RuntimeException with the upstream response body and HTTP status code embedded in the message and code.

try {
    $api->pollDevice(99999);
} catch (\RuntimeException $e) {
    error_log("Poll failed: HTTP {$e->getCode()}{$e->getMessage()}");
}

Status codes you should handle distinctly:

Code When What to do
401 token expired/invalid re-login, then retry
403 token lacks the required permission escalate to whoever provisioned the API user
404 resource doesn't exist OR isn't visible to this user inspect ID; check role
422 validation failed on a POST/PUT parse errors from the response body
429 rate-limited sleep & retry with exponential backoff
5xx server-side problem retry with backoff; alert if persistent

A defensive wrapper:

function callWithRetry(callable $fn, int $maxAttempts = 3): mixed
{
    $attempt = 0;
    do {
        try {
            return $fn();
        } catch (\RuntimeException $e) {
            $code = $e->getCode();
            if ($code >= 500 || $code === 429) {
                if (++$attempt >= $maxAttempts) throw $e;
                sleep(2 ** $attempt);
                continue;
            }
            throw $e;
        }
    } while (true);
}

$device = callWithRetry(fn () => $api->device(42));

Custom HTTP options (timeouts, proxy, TLS)

The third constructor argument is merged into the underlying HTTP client options, so anything the HTTP client supports works:

$api = new AcsMonitorClient('https://monitoring.example.com', null, [
    'timeout' => 60,
    'connect_timeout' => 5,
    'verify' => '/etc/ssl/certs/ca-certificates.crt', // custom CA bundle
    'proxy' => 'http://proxy.local:3128',
    'headers' => [
        'User-Agent' => 'MyIntegration/1.0',
    ],
]);

For testing against a self-signed dev box, set 'verify' => false but never in production.

Token storage best practice

Production integrations should:

  1. Provision a dedicated API user in ACS Monitor with the minimum permission scope needed (often viewer plus monitors.run_tools for triggering polls/checks).
  2. Generate a long-lived personal access token for that user.
  3. Store the token in your secret manager (HashiCorp Vault, AWS Secrets Manager, Doppler, .env.gpg, …) — never in code or git.
  4. Pass it via env var to your application so the same client code works in dev / staging / prod with zero code changes.
  5. Rotate periodically by issuing a new token, deploying it, then revoking the old one with $api->logout() (or via the UI).

Recipes

Sync ACS Monitor devices into a CMDB

foreach ($api->devices() as $device) {
    Cmdb::upsertAsset([
        'external_id' => "acsmon-{$device['id']}",
        'name' => $device['name'],
        'ip_address' => $device['ip_address'],
        'tags' => ['acsmon', $device['group']['name'] ?? 'ungrouped'],
        'last_seen_at' => $device['last_polled_at'],
    ]);
}

Forward critical alerts to PagerDuty

$query = [
    'filter[status]' => 'open',
    'filter[severity]' => 'critical',
    'sort' => '-triggered_at',
];

foreach ($api->alertEvents($query) as $event) {
    PagerDuty::createIncident([
        'dedup_key' => "acsmon-{$event['id']}",
        'summary' => $event['title'] ?? $event['message'],
        'source' => $event['device']['name'] ?? $event['monitor']['name'],
        'severity' => 'critical',
    ]);
    $api->acknowledgeAlertEvent($event['id'], 'Forwarded to PagerDuty');
}

CI/CD post-deploy verification

// After deploying app, immediately check that its monitor still passes
$result = $api->checkMonitor(getenv('ACSMON_DEPLOY_MONITOR_ID'));
if ($result['status'] !== 'up') {
    fwrite(STDERR, "Post-deploy check failed: {$result['error']}\n");
    exit(1);
}
echo "Health check passed in {$result['response_time_ms']}ms\n";

Weekly uptime report

$report = [];
foreach ($api->monitors(['filter[is_active]' => true]) as $monitor) {
    $u = $api->monitorUptime($monitor['id'], ['days' => 7]);
    $report[] = [
        'name' => $monitor['name'],
        'host' => $monitor['host'],
        'uptime_pct_7d' => $u['uptime_percent'] ?? null,
    ];
}
usort($report, fn ($a, $b) => $a['uptime_pct_7d'] <=> $b['uptime_pct_7d']);
// … render to email / Slack / wherever

Troubleshooting

Login returns 422 "The given data was invalid" Most often email or password is missing/wrong. The error response body contains the per-field complaint.

Calls return 401 even with a token set The token has been revoked or the user account was disabled. Re-login.

Calls return 403 Authentication is fine but the user lacks the necessary permission. Either grant the role/permission in the UI or have the integration act as a user that already has it.

Iterator methods yield nothing Likely a too-restrictive filter[] query, or you're targeting a user that doesn't have visibility on any devices/monitors. Try without filters first.

Long-running scripts get cut off mid-fetch Bump 'timeout' in the constructor's options array. The default 30s covers normal use; uptime queries spanning months can take longer.

Self-signed cert errors on the dev box Pass 'verify' => false in the constructor options (dev only) or provide a custom CA bundle path.


© Anglia Computer Solutions Ltd. — ACS Monitor is a product of Anglia Computer Solutions. Visit acsmon.com for documentation, demos and licensing.

About

Official PHP 8.1+ client for the ACS Monitor REST API.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages