APIs we call

Referenceby Aaron Reimannupdated Sep 28, 2026#reference#api#integrations

Every outbound HTTP call this app makes, grouped by service. If something feels mysterious (“where does this number come from?”), the source is almost certainly in this list. Endpoints are the exact paths the corresponding *Client.php actually hits — kept synchronized across core services and modules by the tracks: glob above.

DigitalOcean — https://api.digitalocean.com/v2

modules/DigitalOcean/src/DigitalOceanClient.php · Bearer CLOCKWORK_DIGITALOCEAN_TOKEN

Method Path What it does
GET /account Sanity-check the token (clockwork:digitalocean-test).
GET /droplets List every droplet, paginated 200 at a time. Used for IP↔server matching during import.
GET /monitoring/metrics/droplet/cpu Per-droplet CPU jiffy series.
GET /monitoring/metrics/droplet/load_1 1-minute load.
GET /monitoring/metrics/droplet/memory_{free,total,available} Memory series. We use available, not free — see CLAUDE.md gotcha.
GET /monitoring/metrics/droplet/filesystem_{free,size} Disk series.

Driven by clockwork:poll-servers every 5 minutes for servers whose provider='digitalocean'.

Hetzner Cloud — https://api.hetzner.cloud/v1

modules/Hetzner/src/HetznerClient.php · Bearer CLOCKWORK_HETZNER_TOKEN

Method Path What it does
GET /locations Auth probe (Hetzner has no /account). Used by clockwork:hetzner-test and the inventory bootstrap.
GET /servers List every server in the project, paginated (50/page). Used for IP↔server matching during import.
GET /servers/{id}/metrics?type=cpu Per-server CPU%, ready-to-use percentage (no jiffy delta).
GET /servers/{id}/metrics?type=disk Disk IO rate (not capacity %).
GET /servers/{id}/metrics?type=network Network throughput.

Driven by clockwork:poll-servers every 5 minutes for servers whose provider='hetzner'. Hetzner’s metrics surface is narrower than DO’s — no memory, no filesystem-free, no load. Secondary panels stay null on Hetzner servers.

Azure — https://management.azure.com (ARM) + Azure Monitor Metrics

modules/Azure/src/AzureClient.php · OAuth 2.0 client-credentials (Service Principal) against https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token, Bearer token cached in-memory for its lifetime.

Method Path What it does
GET /subscriptions/{id} (api-version=2022-12-01) Auth probe (clockwork:azure-test).
GET /subscriptions/{id}/providers/Microsoft.Compute/virtualMachines (api-version=2024-03-01) List every VM in the subscription, paginated via nextLink. Used for IP↔server matching during import/reconcile.
GET /subscriptions/{id}/providers/Microsoft.Network/publicIPAddresses (api-version=2024-03-01) Public IPs — Azure doesn’t expose them directly on the VM object, so ReconcileProvider cross-references NIC linkage to match by Server::hostname.
GET {resourceId}/providers/microsoft.insights/metrics (api-version=2018-01-01) Per-VM metrics (Percentage CPU, Available Memory Bytes), 1-minute average interval over a caller-supplied timespan.

Third cloud provider alongside DigitalOcean and Hetzner — clockwork:poll-servers and clockwork:reconcile-provider branch on all three. Azure Monitor exposes CPU and memory directly; disk % would need the Azure Monitor Agent installed per-VM, so disk stays null on Azure servers the same way Hetzner’s does. See Integrations → Azure.

Vultr — https://api.vultr.com/v2

modules/Vultr/src/VultrClient.php · Bearer CLOCKWORK_VULTR_API_KEY

Method Path What it does
GET /account Auth probe — /settings/diagnostics Vultr check.
GET /instances List every instance, cursor-paginated (meta.links.next). Used for IP↔server matching during reconcile.

Driven by clockwork:reconcile-provider for IP matching. No metrics endpoint is called — Vultr’s API v2 doesn’t expose CPU/memory/disk time series, so VultrCloudProvider::metrics() always returns nulls and clockwork:poll-servers gets no signal for provider='vultr' servers beyond alive/dead. See Integrations → Vultr.

Linode (Akamai) — https://api.linode.com/v4

modules/Linode/src/LinodeClient.php · Bearer CLOCKWORK_LINODE_TOKEN

Method Path What it does
GET /account Auth probe — /settings/diagnostics Linode check.
GET /linode/instances List every linode, page-paginated. Used for IP↔server matching during reconcile.
GET /linode/instances/{id}/stats CPU time series (data.cpu = [[unix_ts, pct], …], summed across all vCPUs — LinodeCloudProvider::metrics() divides by server.vcpus to normalize to a single-core percentage).

Driven by clockwork:poll-servers (CPU only — memory/disk/load stay null, same gap as Hetzner/Azure) and clockwork:reconcile-provider for IP matching. See Integrations → Linode.

DigitalOcean Spaces — S3-compatible

app/Services/DigitalOcean/SpacesClient.php · HMAC CLOCKWORK_DO_SPACES_KEY + _SECRET (NOT the DO PAT)

S3 ListObjectsV2 against <bucket>/<domain>/. SpinupWP writes backups as <domain>/<YYYY-MM-DD-HH-MM-SS>-<suffix>.{sql.gz|tar.gz}; we group by timestamp into one row per backup run. Used because SpinupWP’s REST API exposes backup config but not history.

SpinupWP — https://api.spinupwp.app/v1

modules/SpinupWp/src/SpinupWpClient.php · Bearer CLOCKWORK_SPINUPWP_TOKEN

Method Path What it does
GET /servers, /servers/{id} Inventory bootstrap + per-server refresh.
GET /sites, /sites/{id} Same for sites. Includes backups sub-object (config only).
GET /sites/{id}/events Recent events; falls back gracefully on older accounts.
POST /sites, /sites/{id}/domains Used by the migration runner to create destination sites and add domains.

Daily import at 03:30 (clockwork:import-spinupwp).

Pressable — https://my.pressable.com/v1

modules/Pressable/src/PressableClient.php · OAuth 2.0 client-credentials against https://my.pressable.com/auth/token, Bearer token cached ~55 of its 60-minute life. No server concept — every call is addressed by site_id alone.

Method Path What it does
GET /account Auth probe (clockwork:pressable-test).
GET /sites Inventory bootstrap (paginated).
GET /sites/{id} Per-site refresh.
DELETE /sites/{id}/edge-cache, /sites/{id}/object-cache Cache-flush buttons on the site detail page.
POST /sites/{id}/wordpress/commands, /sites/{id}/wordpress/wpcli Fire-and-forget shell / wp-cli commands — see PressableCommandRunner for how this becomes synchronous (result read back from the activity log, not the response body).
GET /sites/{id}/backups, /backups/fs, /backups/db Backup run history — clockwork:pressable-backups-report (06:32 daily).
GET /sites/{id}/reports/performance/latest Pressable’s own Lighthouse report — see Integrations → Pressable.
GET /sites/{id}/statistics Page-view stats — clockwork:pressable-traffic-report (06:37 daily).
GET /sites/{id}/security-alerts/{plugins,themes} Known-vulnerability + Defensive Mode status — clockwork:pressable-security-summary-report (06:39 daily), Pressable-only, no SpinupWP equivalent.
POST /sites/{id}/metrics, /sites/{id}/logs/activity CPU/MySQL resource metrics and the activity-log poll PressableCommandRunner uses for command results.

Cloudflare — https://api.cloudflare.com/client/v4

app/Services/Cloudflare/CloudflareClient.php

Two scoped tokens — separated so a leak of the read token can’t mutate DNS:

  • CLOCKWORK_CLOUDFLARE_API_TOKEN — read (Zone:Read + Zone WAF:Read + the per-phase scopes documented in integrations/cloudflare).
  • CLOCKWORK_CLOUDFLARE_WRITE_TOKEN — Zone.DNS:Edit, used only by the migration runner’s cutover phase.
Method Path What it does
GET /zones?name=... Zone lookup by hostname.
GET /zones/{id}/rulesets/phases/{phase}/entrypoint Redirect / Transform / WAF / Config rules.
GET /zones/{id}/pagerules Legacy Page Rules.
GET /zones/{id}/dns_records DNS table for the diagnostic dump.
PATCH/POST /zones/{id}/dns_records[/...] Migration cutover only. Write token required.

Bill.com — https://api.bill.com/api/v2

app/Services/BillCom/BillComClient.php · Session-based: POST /Login.json with username/password/orgId/devKey, cache sessionId for 25 min. The advertised v3 hostname has no DNS — v2 is what works.

POST /List/{Customer,Item,Invoice}.json (paginated 100 at a time, date-filtered for invoices).

Daily at 01:00 + 01:30, gated on CLOCKWORK_BILL_COM_ENABLED.

Companion mu-plugin — https://{domain}/wp-json/clockwork/v1/* (or clockwork-renegade/v1/*)

app/Services/Companion/ClockworkCompanionClient.php · HMAC-SHA256 with per-site secret (X-Clockwork Control-Signature + X-Clockwork Control-Timestamp, 5-min replay window), or 256-bit cryptographic connection key for Clockwork Renegade. Dynamically selects route namespace (clockwork/v1 vs clockwork-renegade/v1).

Routes:

  • Diagnostics & Discovery: /health, /detect, /snapshot, /environment (PHP, MySQL, and WP runtime telemetry), /debug-log (inspector, lines param) + POST /debug-log/clear.
  • Inventory & Admin: /plugins, /admins, /wp-cron, /comments-summary, /lockouts, /wordfence-blocks.
  • Plugin Lifecycle: POST /plugins/toggle (activate/deactivate), POST /plugins/delete, POST /plugins/install (by slug or signed package URL).
  • Updates: POST /updates/plugin, POST /updates/theme, POST /updates/core, POST /updates/translations.
  • Database Maintenance: /database/summary, POST /database/optimize (table optimization and cleanup).
  • Backups & Restores: POST /backups-report, POST /backup/create (direct-to-S3 Glacier streaming backup; Companion rejects non-public upload_url values before dumping), POST /backup/restore/stage, GET /backup/restore/status, POST /backup/restore/apply (two-step off-site Glacier restore; stage download URLs must be public HTTPS with no redirects).
  • Contact Forms & SSO: /form-subscriptions, POST /test-contact-form, POST /sso/link.

Full details on the architecture/companion-plugin page. Control only mints restore/download URLs for keys that BackupArchiveEnumerator::belongsToSite() accepts.

wpvulnerability.net — https://www.wpvulnerability.net/plugin/{slug}

app/Services/Security/WpVulnerabilityClient.php · No auth, keyless. GET per unique plugin slug installed anywhere in the fleet (~150-250 slugs), 100ms between requests, replaces the local plugin_vulnerabilities mirror in one transaction. Per-slug failures are recorded but don’t abort the run. Replaced Wordfence’s free Threat Intelligence v2 feed after it moved to authenticated v3 — wpvulnerability.net aggregates CVE/Patchstack/WPScan/Wordfence into one free feed. Feeds the Issues page’s vulnerable-plugin flags. Daily at 03:15 (clockwork:refresh-plugin-vulnerabilities). Separate from Pressable’s own CVE feed (security-alerts/plugins/themes on PressableClient) — Pressable-hosted sites get both.

WordPress.org Plugin Information — https://api.wordpress.org/plugins/info/1.2/

app/Services/Security/PluginDirectoryClient.php · No auth, keyless. GET ?action=plugin_information&request[slug]={slug} with User-Agent: Clockwork Control-Monitoring/1.0 (+plugin-directory-check). 100ms delay between requests. Queries unique plugin slugs across the fleet to detect closed/abandoned zombieware plugins (plugin_directory_statuses). Tolerates partial failures; non-closed/not_found responses for premium plugins never alert. Weekly on Mondays at 03:30 UTC (clockwork:refresh-closed-plugins).

CISA Known Exploited Vulnerabilities (KEV) — https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json

app/Services/Security/CisaKevClient.php · No auth, keyless official JSON feed (~1.7k active in-the-wild exploitation entries). Single daily download at 03:20 UTC (clockwork:refresh-cisa-kev), atomic swap (cisa_kev_entries) via database transaction and chunked insert. Used at query-time to badge CVEs detected in installed plugins as Actively exploited (CISA KEV).

endoflife.date — https://endoflife.date/api/v1/products/{product}

app/Services/Runtime/EndOfLifeClient.php · No auth, keyless v1 JSON API. Daily sync at 05:10 UTC (clockwork:refresh-runtime-eol) for products /php and /wordpress. Stores parsed release cycles in app_settings for the Capacity dashboard (/capacity) and per-site tech stack widgets without blocking on HTTP at request time.

Sucuri SiteCheck — https://sitecheck.sucuri.net/api/v3/?scan=<url>

No auth, ~30 req/min ceiling — clockwork:scan-sitecheck sleeps 250 ms between sites. Same engine ManageWP resold. Weekly Monday 02:00.

GTmetrix REST v2 — https://gtmetrix.com/api/2.0

modules/GTmetrix/src/GtmetrixClient.php · HTTP Basic, API key as username, blank password. Primary performance engine. POST /tests (JSON:API — Content-Type: application/vnd.api+json is mandatory; location is an integer ID, not a slug) then poll GET /tests/{id} at 5s intervals, 180s ceiling. Completion = data.type flips to report (the state attribute disappears). Nightly 03:30 cron.

Google PageSpeed Insights v5 — https://www.googleapis.com/pagespeedonline/v5/runPagespeed

modules/PageSpeedInsights/src/PageSpeedInsightsClient.php · key=CLOCKWORK_PSI_API_KEY (free 25k/day). Fallback engine — called when GTmetrix encounters an error or reaches capacity; rows tagged engine='psi-fallback'. One request asks for four Lighthouse categories via repeating category= params (performance, accessibility, best-practices, seo). Bracket arrays (category[0]=) are ignored by PSI v5.

Google Cloud Web Risk — https://webrisk.googleapis.com/v1/uris:search

GET with key=CLOCKWORK_GOOGLE_WEB_RISK_KEY and repeating threatTypes=. Primary source for clockwork:check-blacklists. Empty JSON {} means clean.

Google Safe Browsing v4 — https://safebrowsing.googleapis.com/v4/threatMatches:find

POST with key=CLOCKWORK_GOOGLE_SAFE_BROWSING_KEY. Used only when CLOCKWORK_GOOGLE_WEB_RISK_KEY is empty. The v4 secret must not be copied into the Web Risk env var — they are different APIs, and stuffing a v4 key into the Web Risk slot would call webrisk.googleapis.com with a key that API will reject.

URLhaus — https://urlhaus-api.abuse.ch/v1/host/

POST, Auth-Key: CLOCKWORK_URLHAUS_AUTH_KEY (free registration, optional).

Spamhaus DBL — DNS, no API

{domain}.dbl.spamhaus.org lookup. 127.0.1.x return codes mean spam (2), phish (4), malware (5), botnet (6). Always runs, no key needed.

Twilio — SMS, via the official PHP SDK (twilio/sdk)

modules/Twilio/src/TwilioClient.php · Auth is Account SID + Auth Token (TWILIO_ACCOUNT_SID/TWILIO_AUTH_TOKEN), no raw Http:: calls — the SDK’s Client::messages->create() handles the actual api.twilio.com request. Sends one SMS per on-call recipient for care-plan site-down/site-up alerts (App\Services\Twilio\SmsNotifier), mirroring MattermostNotifier’s public surface so UptimeStateUpdater can fire both channels with parallel signatures. Gated on TWILIO_ENABLED — kept false pre-A2P-10DLC-approval to preserve Mattermost-only behavior even with credentials present. Zero on-call recipients falls back to email + a Mattermost @channel warning so an alert is never silently dropped. See Integrations → Twilio.

Mattermost — webhook CLOCKWORK_MATTERMOST_WEBHOOK_URL

modules/Mattermost/src/MattermostNotifier.php · POST {text, channel?, username, icon_emoji, attachments?}. Channel slug must be lowercase — Mattermost silently rejects mixed case.

Google OAuth — accounts.google.com + oauth2.googleapis.com

Via laravel/socialite Google driver. Authorize → token → userinfo. GOOGLE_HD optionally pins to a Workspace domain.

Mailgun — MAILGUN_ENDPOINT (api.mailgun.net | api.eu.mailgun.net)

Symfony Mailgun transport. Only active when MAIL_MAILER=mailgun. Sends contact-form failure / recovery alerts and the monthly summaries.

Arcjet — https://raw.githubusercontent.com/arcjet/well-known-bots/main/well-known-bots.json

Plain GET, no auth. Refreshed daily at 03:00 into allowed_bots.

ICANN RDAP — https://rdap.org

app/Services/Domains/RdapClient.php (RdapClient) · Public ICANN Registration Data Access Protocol (RFC 7483 / 9083), no authentication key required.

Method Path What it does
GET /domain/{domain} Resolves domain registration, registrar vCard, and expiration timestamp. Follows 302 redirects to authoritative TLD registries (e.g. Verisign, PIR).

Driven by clockwork:check-domain-expirations daily at 06:05 UTC. Rate-limited to 10 requests per 10 seconds, with automated 2-hour backoff upon HTTP 429/503 responses from specific TLD endpoints. See Features → Domain expiration tracking.

LM Studio — http://localhost:1234/v1 (loopback only)

OpenAI-compatible local LLM. No data leaves the box. Used by the experimental nginx-log threat analyzer.

Out-of-process: SSH

Not HTTP, but worth listing here because it is by far the highest-volume external dependency. Every server we manage is reached via phpseclib3 against the user in CLOCKWORK_DEFAULT_SSH_USER (default clockwork-deploy). See integrations/ssh-and-fail2ban for the auth precedence and the commands we run.

Clockwork Web Dev
Brought to you by Clockwork Web Dev

A real WordPress development agency building battle-tested operational tooling to run client fleets with zero bloat and total control.

Visit Clockwork Web Dev→