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
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
Make your first request
Every call is a
POSTwith a JSON body and a Bearer token. Start withservices.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"}'- 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.
| Plan | Requests / minute | Monthly quota |
|---|---|---|
| Free | No API access | |
| Pro | 300 | 100,000 |
| Business | 1,000 | 1,000,000 |
| Enterprise | Custom | Custom |
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.
services.listReturn 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 }
}services.getFetch 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.
incidents.currentCurrently 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 }
}incidents.historyRecent incidents (resolved and still open), newest first, for one service or every service.
Parameters
serviceIdstring · optional. Filter to a single servicelimitinteger · 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.
metrics.bucketsMost recent 15-minute report buckets for a service (baseline and severity included).
Parameters
serviceIdstring · required. Service to querylimitinteger · 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.
watchlists.listWatchlists 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.
alerts.rules.createCreate an alert rule for the user linked to this API key (plan limits apply).
Parameters
serviceIdstring · required. Service to alert ondeliveryChannelstring · 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.
| Status | Meaning |
|---|---|
| 400 | Bad request — missing or invalid parameters, a missing or unknown endpoint name (unknown_endpoint), or an apiVersion this deployment does not know (unsupported_api_version). |
| 401 | Missing Authorization header (unauthorized), or an API key that is unknown or revoked (invalid_key). |
| 403 | Account inactive or plan does not include API access (account_inactive), or a plan limit reached (plan_limit). |
| 404 | Resource not found, such as an unknown service (service_not_found). |
| 429 | Rate limit or monthly quota exceeded, or concurrent requests on one key collided (rate_contention). Back off and retry; honor Retry-After when present. |
| 500 | Unexpected server error — retry with exponential backoff. |
| 503 | Temporarily 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 } }