---
title: WatchFor Prometheus metrics endpoint and Grafana dashboard
description: Scrape monitor status, uptime, response times, certificate and domain expiry, heartbeats and hosts from WatchFor into your own Prometheus, with a ready-made Grafana dashboard. Pro plan and above.
canonical: https://watchfor.io/docs/api/prometheus
---

# WatchFor Prometheus metrics endpoint and Grafana dashboard

Scrape monitor status, uptime, response times, certificate and domain expiry, heartbeats and hosts from WatchFor into your own Prometheus, with a ready-made Grafana dashboard. Pro plan and above.

| Method | Path | Scope | operationId |
| --- | --- | --- | --- |
| GET | `/v1/metrics` | read | `getPrometheusMetrics` |

If your team already lives in Prometheus and Grafana, you don't have to
open WatchFor to see what it sees. `GET /v1/metrics` answers in the
Prometheus text format, so your Prometheus scrapes it like any other
target: monitor status, uptime over 24 hours, 7 and 30 days, the latest
check and 24-hour response times, certificate and domain expiry, heartbeat
check-ins, hosts, fleet health and open incidents. The numbers are the
dashboard's numbers. Uptime counts confirmed critical downtime only, with
maintenance excluded, exactly like the monitor page.

> **Pro plan and above**
>
> The endpoint is part of the Pro, Business and Enterprise plans. On other
> plans it answers `403` with an upgrade message. Any API key of the
> organization works, and a `read` key is enough.

## Scrape it

Create a `read` key under **Settings → API keys**, then add a job to
`prometheus.yml`:

```yaml
scrape_configs:
  - job_name: watchfor
    scrape_interval: 60s
    scheme: https
    metrics_path: /api/v1/metrics
    authorization:
      credentials_file: /etc/prometheus/watchfor-api-key # wf_live_…
    static_configs:
      - targets: ["watchfor.io"]
```

Grafana Cloud and any Prometheus-compatible agent (Grafana Alloy, the
OpenTelemetry Collector's Prometheus receiver, VictoriaMetrics `vmagent`)
use the same settings: the URL, a 60-second interval and the bearer token.

Narrow a scrape with query parameters. Each one can be repeated or given
comma-separated values:

| Parameter | Effect |
| --- | --- |
| `tag` | Only monitors carrying one of these tags, e.g. `?tag=production` |
| `type` | Only these monitor types, e.g. `?type=http,api` |
| `monitor_id` | Only these monitors |
| `include_hosts=false` | Leave the host families out |

Filters narrow the per-monitor series. Organization totals (status counts,
health score, incidents) always cover the whole organization.

## Limits and freshness

- The response is computed at most **once a minute** per organization and
  filter, and served from a cache in between. Scraping faster than every 60
  seconds only returns the same body again.
  `watchfor_scrape_generated_timestamp_seconds` tells you its age.
- The endpoint has its own limit of **30 requests a minute per
  organization**, on top of the key's normal API limit. Past it you get
  `429` with `Retry-After`.
- Errors stay JSON, like the rest of the API: `401` for a missing or
  revoked key, `403` when the plan does not include the endpoint.

## Metrics

Every per-monitor series carries a single `monitor_id` label, and hosts
carry `host_id`. Names, targets and tags are on the `*_info` series only,
so renaming a monitor never splits its history and a monitor costs about a
dozen series. Join them in when you need them:

```text
watchfor_monitor_up * on(monitor_id) group_left(name, type) watchfor_monitor_info
```

### Organization

| Metric | Labels | Meaning |
| --- | --- | --- |
| `watchfor_organization_info` | `organization`, `plan` | Always 1 |
| `watchfor_monitors` | `status` | Monitors by status: up, degraded, down, paused, unknown |
| `watchfor_health_score` | | Fleet health score 0–100, as on the dashboard |
| `watchfor_incidents_firing` | `severity` | Firing incidents, critical or warning |
| `watchfor_incidents_acknowledged` | | Acknowledged incidents that are still open |
| `watchfor_maintenance_windows_active` | | Maintenance windows in progress |
| `watchfor_hosts` | `state` | Hosts online, stale, offline, never reported |

### Monitors

| Metric | Meaning |
| --- | --- |
| `watchfor_monitor_info{name, type, target, tags, strategy}` | Always 1. `tags` is comma-joined. A heartbeat's target is left empty, because its ping URL is a secret |
| `watchfor_monitor_up` | 1 unless a confirmed **critical** incident is open (degraded counts as up). Absent while paused |
| `watchfor_monitor_state` | 0 down, 1 degraded, 2 up, 3 paused, 4 unknown |
| `watchfor_monitor_paused` | 1 when paused |
| `watchfor_monitor_open_incident_severity` | 0 none, 1 warning, 2 critical |
| `watchfor_monitor_check_interval_seconds` | Configured interval |
| `watchfor_monitor_last_check_timestamp_seconds` | When the latest check ran (last 6 hours) |
| `watchfor_monitor_last_check_success` | Raw result of the latest check from one location |
| `watchfor_monitor_last_check_duration_seconds` | Duration of the latest check |
| `watchfor_monitor_checks_24h{result}` | Checks in the last 24 hours, `success` or `failure` |
| `watchfor_monitor_avg_duration_24h_seconds` | Mean check duration over 24 hours |
| `watchfor_monitor_uptime_ratio{window}` | 0–1 for `24h`, `7d`, `30d`, incident-based |
| `watchfor_ssl_certificate_expiry_timestamp_seconds` | SSL monitors: when the certificate expires |
| `watchfor_domain_expiry_timestamp_seconds` | Domain monitors: when the registration expires |
| `watchfor_heartbeat_last_ping_timestamp_seconds` | Heartbeat monitors: last real check-in |

### Hosts

| Metric | Meaning |
| --- | --- |
| `watchfor_host_info{name, os, arch, agent_version}` | Always 1 |
| `watchfor_host_up` | 1 when the agent reported within its liveness window |
| `watchfor_host_last_seen_timestamp_seconds` | The agent's last push |

## Alerting from Prometheus

WatchFor already confirms an outage from several locations before it opens
an incident, so alert on `watchfor_monitor_up`, not on single checks. A
failed check from one location (`watchfor_monitor_last_check_success`) is
not an outage.

```yaml
groups:
  - name: watchfor
    rules:
      - alert: WatchForMonitorDown
        expr: watchfor_monitor_up == 0
        for: 1m
        annotations:
          summary: "{{ $labels.monitor_id }} is down (confirmed by WatchFor)"
      - alert: CertificateExpiresSoon
        expr: (watchfor_ssl_certificate_expiry_timestamp_seconds - time()) / 86400 < 14
        annotations:
          summary: "Certificate expires in {{ $value | humanize }} days"
      - alert: SLOBudgetAtRisk
        expr: watchfor_monitor_uptime_ratio{window="30d"} < 0.999
        annotations:
          summary: "30-day uptime below 99.9%"
```

## Grafana dashboard

A ready-made dashboard covers fleet health, a status timeline per monitor,
uptime against an SLO target you pick, response times, certificate and
domain expiry, heartbeats and hosts, with filters by type, tag and monitor.
Download [watchfor-overview.json](/grafana/watchfor-overview.json) and
scrape the endpoint directly and through the exporter below. The blog post
[Uptime monitoring in Grafana](/blog/prometheus-grafana-uptime-monitoring)
walks through it with screenshots and the queries worth keeping.

## Prefer an exporter?

Prometheus can scrape the endpoint directly, as shown above. The
open-source [WatchFor Prometheus exporter](https://github.com/watchfor-io/prometheus-exporter)
is for setups where that doesn't fit: several WatchFor organizations behind
one target, keys kept in a Kubernetes secret instead of the Prometheus
config, or a scrape target inside your cluster. It reads this endpoint
(never the REST API), adds an `organization` label and reports its own
health.

---

Canonical page: https://watchfor.io/docs/api/prometheus · All docs: https://watchfor.io/docs · Site guide: https://watchfor.io/llms.txt
