Skip to content
Developer API · v1

PulseWatch API reference

Programmatic access to outage intelligence. Query services, incidents, and metrics — or wire PulseWatch directly into your on-call tooling.

Overview

The PulseWatch API exposes the same data that powers your dashboards — monitored services, detected incidents, 15-minute metric buckets, and alert configuration — behind a single authenticated HTTPS endpoint.

JSON over HTTPS

Every request is a POST with a JSON body. Every response is JSON.

Bearer token auth

One API key per integration, rotatable from the dashboard.

Webhook-native

Every incident event can also be delivered to your URL as a signed webhook.

All requests hit a single dispatch endpoint. The operation is chosen via theendpoint field in the JSON body — for example "services.list" or"incidents.current".

Quickstart

  1. 1

    Create an API key

    Go to Dashboard → API keys and generate a new key. Keys start with pw_live_ and are shown exactly once.

  2. 2

    Make your first request

    Every call is a POST with a JSON body and a Bearer token. Start with services.list:

curl -X POST https://pulsewatch.us/functions/publicApi \
  -H "Authorization: Bearer pw_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"endpoint": "services.list"}'
  1. 3

    Wire up alerts (optional)

    Create an Alert rule in the dashboard and point it at a webhook URL to receive incident lifecycle events directly.

Authentication

Authenticate every request with a Bearer token. Generate and revoke keys from theAPI keyspage. Full keys are shown only at creation time — if lost, revoke and regenerate.

Authorization: Bearer pw_live_<32 characters>

Key hygiene

Never commit keys to source control. Rotate keys after any suspected exposure — revoked keys stop working immediately and do not grant a grace period.

Rate limits & quotas

Limits apply per API key. Both per-minute request caps and monthly request quotas are enforced.

PlanRequests / minuteMonthly quota
FreeNo API access
Pro300100,000
Business1,0001,000,000
EnterpriseCustomCustom

When limits are exceeded, the API returns 429 Too Many Requests. Back off exponentially and retry after the rate window resets (per-minute windows are 60 seconds). When a Retry-After header is present, wait at least that many seconds.

If PulseWatch is briefly over its own platform capacity, the API returns 503 Service Unavailable with error code temporarily_unavailable and a Retry-After header in seconds. Wait at least that long before retrying. A request answered with 503 may already have counted toward your per-minute limit and monthly quota.

Plan requirement

API keys can be created on Pro and Business plans. Requests made with a key whose account is on the Free plan, has no active subscription, or whose plan does not include API access return 403 account_inactive.

Pagination

Every successful response wraps its result in data. List endpoints accept an optional limit: services.list up to 1,000 (default 500), incidents.current up to 500 (default 200), incidents.history up to 100 (default 50), metrics.buckets up to 288 (default 96). services.list also accepts offset; walk pages untilmeta.hasMore is false. Cursor pagination is not available.

{
  "endpoint": "services.list",
  "limit": 500,
  "offset": 500
}

Services

Read data about every service PulseWatch monitors.

Which status field to read

Read displayStatus: major_issue, possible_issue, operational, unknown, not_monitored_sufficiently or retired. Only operational is a healthy conclusion.monitoringConclusion and monitoringReason explain it, andevidenceAsOf is when the official status poll behind it was read (null when there is none). An operational conclusion is reported as unknown when the monitoring job has not re-confirmed it in the last 65 minutes or its official status poll is more than 150 minutes old. It is reported indisplayStatus and evidenceStatus;monitoringConclusion is the stored value from the last evaluation and can still readoperational in that case.evidenceStatus is normal only when displayStatus is operational,unknown when there is no current conclusion, or the issue status.

currentStatus is the older stored field, kept unchanged for existing callers: it readsnormal for a service with no recorded issue even when nothing currently confirms that, so it is not an evidence-based value. Do not read it as health — readdisplayStatus or evidenceStatus.

Field versions

Send apiVersion: "2026-09-15" in the request body (or as a query parameter) for the corrected semantics: currentStatus then carries the evidence-based value, which includesunknown, and incidents marked false positive are left out ofincidents.current and incidents.history. Omit it and nothing changes for your integration: the stored currentStatus and every incident, false positives included (each carrying falsePositive), are returned as they were before 15 September 2026. Once your key and version are accepted, the answer echoes the version it applied in theX-PulseWatch-Api-Version header — success and endpoint errors alike, but not the 401 or 403 raised before your key is read, the 400 below, or a 500. An apiVersion this deployment does not know is refused with400 unsupported_api_version rather than served the older fields.

POSTservices.list

Return monitored services, ordered by name, one page at a time.

Parameters

  • limitinteger · optional. Page size, 1–1000 (default 500)
  • offsetinteger · optional. Rows to skip (default 0)

Request

{
  "endpoint": "services.list",
  "limit": 500,
  "offset": 0
}

Response

{
  "data": [
    {
      "id": "svc_aws",
      "name": "AWS",
      "slug": "aws",
      "category": "cloud",
      "description": "Cloud computing platform",
      "website": "https://aws.amazon.com",
      "currentStatus": "normal",
      "evidenceStatus": "normal",
      "monitoringConclusion": "operational",
      "monitoringReason": "Fresh official operational result confirmed by a healthy reachability check from the PulseWatch runtime (single region, HTTP reachability only).",
      "displayStatus": "operational",
      "evidenceAsOf": "2026-04-23T14:01:12.000Z",
      "monitoringEvaluatedAt": "2026-04-23T09:48:03.000Z"
    }
  ],
  "meta": { "total": 1200, "limit": 500, "offset": 0, "hasMore": true }
}
POSTservices.get

Fetch a single service by its slug.

Parameters

  • slugstring · required. URL-safe service identifier (e.g. 'stripe')

Request

{
  "endpoint": "services.get",
  "slug": "stripe"
}

Response

{
  "data": {
    "id": "svc_stripe",
    "name": "Stripe",
    "slug": "stripe",
    "category": "payments",
    "currentStatus": "normal",
    "evidenceStatus": "unknown",
    "monitoringConclusion": "unknown",
    "monitoringReason": "The vendor reports operational; no fresh independent reachability check confirms it.",
    "displayStatus": "unknown",
    "evidenceAsOf": "2026-04-23T14:01:12.000Z",
    "monitoringEvaluatedAt": "2026-04-23T13:33:05.000Z"
  }
}

Incidents

Incidents are recorded disruptions with start, end, status and severity. Each incident also says how it was established: corroborated is true only when its evidence gate passed with two or more independent origins; singleOrigin marks an incident held on one source;evidenceDecision is the gate's decision path (null when none was stored);officialConfirmedAt is when the vendor confirmed it, if it did. An incident later found to be a false positive carries falsePositive: true and is still returned unless you sendapiVersion: "2026-09-15", which leaves it out.

POSTincidents.current

Currently open incidents across every monitored service, newest first.

Parameters

  • limitinteger · optional. Page size, 1–500 (default 200)

Request

{
  "endpoint": "incidents.current"
}

Response

{
  "data": [
    {
      "id": "inc_123",
      "serviceId": "svc_stripe",
      "serviceName": "Stripe",
      "startedAt": "2026-04-23T14:32:11Z",
      "endedAt": null,
      "status": "major_issue",
      "severity": "high",
      "peakReports": 189,
      "baselineValue": 8,
      "currentValue": 140,
      "summary": "Checkout API errors",
      "detectionMethod": "rolling_baseline_v2_correlated",
      "falsePositive": false,
      "singleOrigin": false,
      "evidenceDecision": "corroborated",
      "corroborated": true,
      "officialConfirmedAt": "2026-04-23T14:40:02Z"
    }
  ],
  "meta": { "total": 1, "limit": 200, "hasMore": false }
}
POSTincidents.history

Recent incidents (resolved and still open), newest first, for one service or every service.

Parameters

  • serviceIdstring · optional. Filter to a single service
  • limitinteger · optional. Number of incidents, 1–100 (default 50)

Request

{
  "endpoint": "incidents.history",
  "serviceId": "svc_stripe",
  "limit": 50
}

Response

{
  "data": [
    {
      "id": "inc_98",
      "serviceId": "svc_stripe",
      "serviceName": "Stripe",
      "startedAt": "2026-04-20T09:10:00Z",
      "endedAt": "2026-04-20T09:48:00Z",
      "status": "resolved",
      "severity": "medium",
      "peakReports": 412,
      "detectionMethod": "vendor_statement_single_origin",
      "falsePositive": false,
      "singleOrigin": true,
      "evidenceDecision": null,
      "corroborated": false,
      "officialConfirmedAt": null
    }
  ]
}

Metrics

Detection metrics for any service, aggregated into 15-minute buckets with baseline and severity scores.

POSTmetrics.buckets

Most recent 15-minute report buckets for a service (baseline and severity included).

Parameters

  • serviceIdstring · required. Service to query
  • limitinteger · optional. Number of buckets, 1–288 (default 96 = 24 hours)

Request

{
  "endpoint": "metrics.buckets",
  "serviceId": "svc_stripe",
  "limit": 96
}

Response

{
  "data": [
    {
      "serviceId": "svc_stripe",
      "pointInTime": "2026-04-23T14:00:00Z",
      "intervalMinutes": 15,
      "totalReports": 42,
      "uniqueSessions": 31,
      "baselineValue": 8,
      "severityScore": 5.25
    }
  ]
}

Watchlists

Watchlists group services together so you can focus dashboards and alerts on the dependencies that matter to you.

POSTwatchlists.list

Watchlists owned by the user linked to this API key. Watchlist members are not included.

Request

{
  "endpoint": "watchlists.list"
}

Response

{
  "data": [
    {
      "id": "wl_1",
      "userId": "user_1",
      "name": "Production stack",
      "active": true
    }
  ]
}

Alerts & webhooks

Alert rules are configured in the dashboard. Use the API to audit or script rule state.

POSTalerts.rules.create

Create an alert rule for the user linked to this API key (plan limits apply).

Parameters

  • serviceIdstring · required. Service to alert on
  • deliveryChannelstring · optional. email, webhook or in_app (default in_app)
  • thresholdTypestring · optional. possible_issue, major_issue (default) or any_change

Request

{
  "endpoint": "alerts.rules.create",
  "serviceId": "svc_stripe",
  "deliveryChannel": "email",
  "thresholdType": "major_issue"
}

Response

{
  "data": {
    "id": "ar_1",
    "serviceId": "svc_stripe",
    "deliveryChannel": "email",
    "thresholdType": "major_issue",
    "enabled": true
  }
}

Webhook deliveries

PulseWatch POSTs a signed JSON payload to your webhook URL on each triggered alert. Payloads include event.type, incident, and service. Every request includes anX-PulseWatch-Signature header so you can verify authenticity (see Webhooks in the dashboard for signature format).

Errors

All errors return a structured JSON body with an error code and a human-readable message. Always log both.

StatusMeaning
400Bad request — missing or invalid parameters, a missing or unknown endpoint name (unknown_endpoint), or an apiVersion this deployment does not know (unsupported_api_version).
401Missing Authorization header (unauthorized), or an API key that is unknown or revoked (invalid_key).
403Account inactive or plan does not include API access (account_inactive), or a plan limit reached (plan_limit).
404Resource not found, such as an unknown service (service_not_found).
429Rate limit or monthly quota exceeded, or concurrent requests on one key collided (rate_contention). Back off and retry; honor Retry-After when present.
500Unexpected server error — retry with exponential backoff.
503Temporarily over capacity (temporarily_unavailable). Retry after the number of seconds in the Retry-After header.

Example error payload

{ "error": { "code": "quota_exceeded", "message": "Monthly quota exceeded", "status": 429 } }