AOD Pulse monitoring API

Create a token in Account settings → API access. Choose only the permissions your script needs, selected monitors or all current and future monitors, and an expiry of 1–365 days. Your password and a fresh second factor are required when two-factor login is enabled. Keep the one-time token private and use it only in trusted server-side tools.

Permissions

Permissions are independent. Existing read-only tokens stay read-only. Up to ten active tokens per account. Revoke tokens in Account settings; password resets revoke all tokens. Signing out or enabling two-factor login does not revoke existing tokens.

Authentication and reads

Send Authorization: Bearer YOUR_TOKEN over HTTPS. Tokens in URLs, browser cookies and cross-origin browser access are not supported.

curl https://aodpulse.com/api/v1/monitors \
  -H 'Authorization: Bearer YOUR_TOKEN'

GET /api/v1/monitors lists authorized monitors. GET /api/v1/monitors/MONITOR_ID selects one. GET /api/v1/history?id=MONITOR_ID&limit=100 returns newest-first history. Pass the returned opaque next_cursor as before until null. Limits are 1–100 rows per request, with 30-day retention. History is not a fixed snapshot; older rows can age out while paging.

Read configuration before editing

GET /api/v1/monitors/MONITOR_ID/configuration requires monitors:config. Its data contains name, URL, paused state, public region names, check options, certificate policy, alert recipient IDs and revision. The ETag is the quoted revision. Protect this private configuration.

Create a monitor

curl -X POST https://aodpulse.com/api/v1/monitors \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: deployment-monitor-20260925' \
  --data '{"name":"My website","url":"https://example.com/","authorized":true}'

Optional check settings inside options: failure_count and recovery_count (1-60 consecutive minutes), verify_tls (default true) and repeat_minutes (0 = off, or 5-1440; re-sends the outage email while it continues). alert_hours (null = always, or {"timezone":"America/Chicago","days":[1,2,3,4,5],"start":"08:00","end":"17:00"}; days 1 = Monday) holds outage and recovery notices until the window opens. Omitted optional settings keep their saved values. For HTTPS websites, auth sets HTTP basic authentication: {"username":"...","password":"..."} to set, {"username":"..."} to rename and keep the stored password, null to remove. Passwords are write-only; configuration returns only username and password_set.

url is a public http:// or https:// website URL (a query string is allowed), ping://host for an ICMP ping check, or tcp://host:port for a TCP port check (public domain or IP address; ports 23, 25, 111, 135-139 and 445 are not accepted). Content checks and certificate reminders apply to websites only.

Creation requires name, url and authorized:true, confirming you own or have permission to monitor the target. Optional fields: regions, options, certificate, alerts. Omitted regions use all currently enabled locations. When selecting regions, use the exact public names shown in your dashboard. Five monitors including paused monitors; checks run every minute. Public HTTP/HTTPS domains and standard ports only; no URL credentials, query strings or fragments.

Edit, pause or delete

curl -X PATCH https://aodpulse.com/api/v1/monitors/MONITOR_ID \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: pause-monitor-20260925' \
  -H 'If-Match: "1"' \
  --data '{"paused":true}'

Use the revision from the latest configuration, not the example value. PATCH supports the creation fields plus paused. Omitted fields stay unchanged. A changed URL requires authorized:true. Supply complete options, certificate or alerts objects when changing them. Set paused to false to resume. DELETE the same monitor URL with an empty JSON object, current If-Match and a fresh idempotency key to permanently remove it and its history.

Advanced objects

{
  "options": {
    "method": "GET", "status_codes": [200],
    "contains": "", "not_contains": "", "timeout": 15,
    "failure_count": 2, "recovery_count": 2
  },
  "certificate": {"enabled": false, "days": [30,14,7]},
  "alerts": {"owner": true, "contacts": []}
}

Method GET or HEAD; content checks require GET and accept up to 512 bytes each. Timeout 1–15 seconds; confirmation counts 1–5 consecutive minutes from every selected location. Certificate reminders require HTTPS and up to five distinct thresholds from 1–90 days. Alert contact IDs must belong to your account and be verified and active. Keep the owner or at least one contact selected. Contact invitations are managed in the dashboard.

Incidents, maintenance and account data

GET /api/v1/me describes the calling token: its permissions, monitor selection, expiry, plan, rate limits and retention. GET /api/v1/regions lists monitoring regions and whether each is currently reporting. Both need only a valid token.

GET /api/v1/incidents (incidents:read) returns incidents newest first. Optional id=MONITOR_ID, state=open or all (default), limit 1–100 and the opaque before cursor. Closed incidents are kept for 90 days. Each incident has acknowledgement: null, or the time, who acknowledged it, the note, and when and by whom the note was last changed.

curl -X POST https://aodpulse.com/api/v1/incidents/INCIDENT_ID/acknowledge \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: ack-2026-10-01-db-failover' \
  -d '{"note":"Investigating: database failover in progress"}'

Acknowledging (incidents:acknowledge) works only on an open incident. The first acknowledgement time and author never change; sending another note replaces the note and records who changed it. An acknowledged incident gets no further repeat reminders by email or webhook. The first outage notice and the recovery notice are still sent. Notes are 1–1000 characters.

GET /api/v1/monitors/MONITOR_ID/results (history:read) returns individual regional check results for the last 24 hours: region, time slot, success, response time, HTTP status and a short error code such as timeout or unexpected_status. Page with limit and before. GET /api/v1/monitors/MONITOR_ID/export downloads 30 days of history as CSV (ten exports per hour, shared with the dashboard).

GET /api/v1/monitors/MONITOR_ID/maintenance (maintenance:read) returns current and upcoming maintenance windows and recurring schedules. With maintenance:write:

Checks continue during maintenance; outage alerts are held. Maintenance writes share an hourly budget of 30 with the dashboard.

GET /api/v1/contacts (contacts:read) lists your alert contacts. GET /api/v1/audit (audit:read) lists your account activity newest first for 90 days, paged with limit and before. Both require a token with access to all current and future monitors. Resources you cannot access return 404, the same as resources that do not exist.

Retries and conflicts

Every write needs a 16–100 character Idempotency-Key using letters, digits, hyphens or underscores. Reuse the same token, key, exact JSON bytes, method, target and If-Match when retrying an uncertain result. Successful results are replayed for 24 hours; Idempotency-Replayed identifies replays. Reusing a key for a different request returns 409. While the first request with a key is still running, a retry with that key returns 409 with Retry-After; retry after that delay to receive the stored result. After 24 hours, reconcile the current monitor state before retrying; do not blindly submit a new create key.

PATCH and DELETE require the current quoted revision in If-Match. The configuration ETag can be sent back as-is, including a weak W/"n" form. Missing or invalid revisions return 428; stale revisions return 412. Read configuration again and deliberately resolve changes before using a new key. Replays return the original minimal result, not current configuration. Expired or revoked tokens cannot replay.

Free tools API

Create a token with the tools:run permission (all-monitor access; up to two such tokens per account) and run the free checks from a script. Tools: headers, redirects, timing, ssl (optional port: 443, 8443, 465, 993 or 995) and health.

curl -X POST https://aodpulse.com/api/v1/public/headers \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"host":"example.com"}'

The reply holds a job id and a poll path; GET it (no token needed) until state is done. Send the token in the Authorization header only; tokens in the address are refused. The health tool combines the latest headers, redirect, SSL and timing results for a name (each under six hours old) into a score and starts no new check; if one is missing the reply lists it in missing so you can run it first.

Limits: 10 requests a minute and 250 a day per token, 300 requests and 500 work units a day per account (headers, redirects and SSL cost 2, timing 3, health 10, an incomplete health reply 1), 2 requests a minute and 10 a day per token for one name, and one running check per token. Responses carry Retry-After, X-Units-Remaining and X-RateLimit-Remaining. Use it only on sites you own or are authorized to test.

Heartbeat start and fail signals

Besides the plain check-in, a job can send POST /api/v1/heartbeat/start when it begins and POST /api/v1/heartbeat/fail when it fails, using the same heartbeat token and an empty body. Pulse then shows how long the last run took, and a reported failure alerts immediately instead of at the deadline.

Webhook alerts

Add up to five HTTPS endpoints in the dashboard under Webhooks. Each endpoint receives confirmed outage and recovery events for all of your websites and heartbeats, or only the ones you select, using the same confirmation, pause and maintenance rules as email alerts. Choose Generic JSON for your own receiver, or Slack, Discord or Microsoft Teams (Workflows) to post a short message to a channel. Endpoint URLs are stored encrypted and never shown again after saving; Slack, Discord and Teams URLs are secrets, so treat them like passwords.

Events: monitor.down, monitor.up, heartbeat.down, heartbeat.up and test (sent from the dashboard). A generic event looks like this; heartbeat events carry a heartbeat object with id, name, period and grace instead of monitor:

{
  "id": "6f1c0e5a9b2d4c7e8f90a1b2c3d4e5f6",
  "type": "monitor.down",
  "occurred_at": "2026-09-26T18:04:00Z",
  "monitor": {"id": "MONITOR_ID", "name": "Shop", "url": "https://example.org/"},
  "incident": {"id": "INCIDENT_ID", "started_at": "2026-09-26T18:02:00Z", "ended_at": null},
  "dashboard_url": "https://aodpulse.com/app"
}

Verify every request

Every request is a POST with Content-Type: application/json and these headers: Pulse-Webhook-Id (the event ID, stable across retries), Pulse-Webhook-Timestamp (Unix seconds), Pulse-Webhook-Event and Pulse-Signature. The signature is v1= followed by the hex HMAC-SHA256 of timestamp + "." + id + "." + raw body, keyed with the endpoint’s signing secret (the full whsec_… string, shown once when you create the endpoint or rotate the secret). Compute it over the raw bytes you received, before any JSON parsing, compare in constant time, and reject timestamps more than five minutes old. For 24 hours after a rotation the header carries two comma-separated signatures, one per secret; accept the request if either matches.

import hashlib, hmac, time

def verify(secret, headers, raw_body):
    ts = headers["Pulse-Webhook-Timestamp"]
    if abs(time.time() - int(ts)) > 300:
        return False                      # older than five minutes
    message = (ts + "." + headers["Pulse-Webhook-Id"] + ".").encode() + raw_body
    expected = "v1=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, sig.strip())
               for sig in headers["Pulse-Signature"].split(","))
const crypto = require("crypto");
function verify(secret, headers, rawBody) {          // rawBody: Buffer
  const ts = headers["pulse-webhook-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const mac = crypto.createHmac("sha256", secret)
    .update(ts + "." + headers["pulse-webhook-id"] + ".").update(rawBody).digest("hex");
  const expected = Buffer.from("v1=" + mac);
  return headers["pulse-signature"].split(",").some(s => {
    const got = Buffer.from(s.trim());
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

Delivery, retries and limits

Respond with any 2xx status within 10 seconds; the response body is ignored. Redirects are not followed and count as a failed attempt. A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours (six attempts over about 8.6 hours). Delivery is at least once and events can arrive out of order, so deduplicate by Pulse-Webhook-Id and use type, occurred_at and the incident ID to order them. After 20 deliveries in a row fail, the endpoint is paused and the account owner receives one email; fix the receiver and enable it again in the dashboard. Events raised while an endpoint is paused are not sent later.

Endpoints must use HTTPS on port 443 and a public domain name; IP addresses, private or internal names, and names that resolve to private, loopback or link-local addresses are refused at every attempt. Requests come from AOD Pulse monitoring nodes whose addresses can change, so rely on the signature rather than an IP allowlist. The Recent deliveries view shows the last 20 deliveries per endpoint with status and error code.

Limits and errors

All API calls share 30 requests per minute per account. Writes additionally allow ten per minute and share the dashboard’s sixty-per-hour write budget. Retries count. Platform and source-address limits may apply sooner. Responses use JSON; 400 invalid input, 401 invalid credentials, 403 missing permission, 404 inaccessible monitor, 409 conflict or plan limit, 412 stale revision, 413 body too large, 415 wrong content type, 428 missing revision, 429 rate limit and 503 platform capacity. Honor Retry-After on 429 and on an in-progress 409. Write bodies must be JSON objects no larger than 16 KiB.

Write responses contain the monitor ID, message and new revision (except deletion); reads use a data envelope. No automatic upgrade of your plan or paid charge occurs. This API supports monitor lifecycle and reads; maintenance management, contact invitations and other dashboard workflows are not yet available through API endpoints.