Official PHP SDK for Cronitorex: cron job, heartbeat and uptime monitoring. Ping API and Management API clients with typed errors, on any PSR-18 HTTP client.

Maintainers

Package info

github.com/cronitorex/php

Homepage

Documentation

pkg:composer/cronitorex/php

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.3.0 2026-08-05 17:19 UTC

This package is auto-updated.

Last update: 2026-08-05 17:27:12 UTC


README

tests Latest version Downloads PHP

Official PHP SDK for Cronitorex: cron job, heartbeat and uptime monitoring. Works with any framework or none - the only transport requirement is a PSR-18 HTTP client (Guzzle works out of the box).

Using Laravel? The cronitorex/laravel package instruments the Scheduler automatically; this SDK is the universal building block for everything else.

Installation

composer require cronitorex/php

If your project has no PSR-18 client yet, add one (Guzzle is the usual choice):

composer require guzzlehttp/guzzle

Wrap a job

An ingest key (ck_, from the panel) is all you need. Monitors are created automatically on first ping. The fastest way to monitor a job is to wrap it:

use Cronitorex\Cronitorex;

$cx = new Cronitorex(ingestKey: 'ck_your_key');

$cx->job('db-backup', function () {
    // ... do the work ...
});

job() sends run before your callable, complete with the measured duration after it, and fail with the exception message if it throws. All three pings share a generated series id, so overlapping runs never pair up wrong. The callable's return value is passed through, its exceptions are rethrown - only the pings themselves are fail-safe.

Pings (dead man's switch)

For full control, send the pings yourself:

use Cronitorex\Cronitorex;
use Cronitorex\Enum\PingStatus;

$cx = new Cronitorex(ingestKey: 'ck_your_key');

$series = uniqid();
$cx->ping('db-backup', PingStatus::Run, series: $series);

// ... do the work ...

$cx->ping('db-backup', PingStatus::Complete, series: $series, duration: 47.3, exitCode: 0);

ping() never throws and never blocks for long: monitoring must not be able to take down the job it monitors. It returns false when the ping could not be sent. If you want the exception instead (in a monitoring-critical path), use pingOrFail(). Stream and custom events go through event(), which is fail-safe the same way (with eventOrFail() as the throwing variant):

$cx->event('deploys', state: 'started', host: gethostname());

Management API

Add a management key (mk_, scopes read + write) to manage monitors as code. Both keys are optional and independent - pass only what you use.

use Cronitorex\Enum\MonitorKind;

$cx = new Cronitorex(managementKey: 'mk_your_key');

$account = $cx->account();                       // plan, monitors used/limit
$list = $cx->monitors()->list(MonitorKind::Ping);

$cx->monitors()->create([
    'manifest_version' => 1,
    'kind' => 'ping',
    'name' => 'db-backup',
    'enabled' => true,
    'expected_interval_seconds' => 86400,
    'grace_seconds' => 3600,
    'tags' => ['production'],
]);

$bundle = $cx->monitors()->export();             // whole account as a bundle
$report = $cx->monitors()->apply($bundle, dryRun: true);

$cx->monitors()->delete($uuid, confirm: true);   // without confirm: refused locally

For the common case - "make sure this cron job has a monitor with sane settings" - use ensure(), an idempotent upsert by name:

// derives expected_interval_seconds from the schedule and a grace of
// interval/2 clamped to [2 min, 1 h]; needs dragonmantank/cron-expression
$cx->monitors()->ensure('db-backup', cronExpression: '0 3 * * *');

// or state the interval yourself - no extra package needed
$cx->monitors()->ensure('db-backup', intervalSeconds: 86400);

// computed fields can be overridden
$cx->monitors()->ensure('db-backup', intervalSeconds: 86400, extra: ['tags' => ['production']]);

Manifests are plain arrays shaped exactly like the API contract - the server is the single source of validation truth, so the SDK never gets stale on manifest rules.

Config as code

Keep your monitors in a version-controlled file and apply it on deploy. YAML and JSON are supported by extension; YAML needs composer require symfony/yaml:

// validate the file in CI (no changes are made)
$report = $cx->monitors()->applyFromFile('monitors.yaml', dryRun: true);

// apply it on deploy
$report = $cx->monitors()->applyFromFile('monitors.yaml');

// snapshot the current account state back into a file
$cx->monitors()->exportToFile('monitors.yaml');
# monitors.yaml
manifest_version: 1
monitors:
  - kind: ping
    name: db-backup
    expected_interval_seconds: 86400
    grace_seconds: 3600

Errors

Management calls throw typed exceptions with stable, machine-readable codes (never match on message text):

use Cronitorex\Enum\ErrorCode;
use Cronitorex\Exception\{ValidationException, AuthException, RateLimitException,
    ConfirmRequiredException, ConfigAsCodeDisabledException, TransportException};

try {
    $cx->monitors()->create($manifest);
} catch (ValidationException $e) {
    // $e->errorCodes() => ['schedule' => [ErrorCode::ScheduleInvalid]]
    // $e->errors()     => human-readable messages per field
} catch (AuthException $e) {
    // bad key, or a scoped key missing the needed scope
} catch (RateLimitException $e) {
    // 60 req/min per management key
}

Unknown error codes map to ErrorCode::Unknown instead of failing, so new server-side codes never break existing SDK versions.

Configuration

Everything is a constructor argument; nothing is global:

Argument Default Purpose
ingestKey null ck_ key; without it ping() returns false
managementKey null mk_ key; without it monitors()/account() throw locally
ingestUrl https://api.cronitorex.com Ping API base
apiUrl https://app.cronitorex.com/api/v1 Management API base
httpClient auto-discovered Any PSR-18 client; bring your own to control timeouts
requestFactory / streamFactory auto-discovered Any PSR-17 factories
logger null Any PSR-3 logger; fail-safe methods (ping(), event(), health()) log swallowed failures as warnings

Every exception the SDK throws implements Cronitorex\Exception\CronitorexExceptionInterface, so one catch block covers them all.

Timeouts

PSR-18 has no timeout API, so timeouts belong to the HTTP client. When the SDK constructs the default Guzzle client itself, it sets a 2 s connect / 5 s request timeout - short on purpose, because ping() runs synchronously inside the job it monitors, and a down monitoring endpoint must cost seconds, not tens. If you inject your own client, its configuration wins and the timeouts become your responsibility:

$cx = new Cronitorex(
    ingestKey: 'ck_your_key',
    httpClient: new \GuzzleHttp\Client(['connect_timeout' => 1, 'timeout' => 2]),
);

Migrating from cronitor-php

The ping vocabulary is intentionally compatible (run / complete / fail / skip, duration, exit_code, host, series), so most call sites map one to one:

cronitor-php cronitorex/php
new Cronitor\Client($apiKey) new Cronitorex(ingestKey: 'ck_...', managementKey: 'mk_...')
$client->ping('key', ['state' => 'run']) $cx->ping('name', PingStatus::Run)
$client->job('key', $fn) $cx->job('name', $fn)
$client->applyConfig() $cx->monitors()->applyFromFile('monitors.yaml')
$client->validateConfig() $cx->monitors()->applyFromFile('monitors.yaml', dryRun: true)
$client->generateConfig() $cx->monitors()->exportToFile('monitors.yaml')

Differences to know about: monitors are addressed by name (created automatically on first ping), errors are typed exceptions with stable ErrorCode enums instead of raw responses, and the transport is any PSR-18 client rather than bundled cURL. See the migration guide for the platform-level differences.

Requirements

PHP 8.1+, ext-json, any PSR-18 HTTP client.

Links

License

MIT