The Pulseback provider manages what you’d otherwise click in the dashboard, through the same REST API. Plan limits apply exactly as they do in the app. The full reference for every argument is on the Terraform Registry.
Install and authenticate
Create a workspace API key in Settings → API keys (admins only). Your workspace id is the ws_… part of the dashboard URL (https://app.pulseback.io/w/<workspace_id>).
terraform {
required_providers {
pulseback = {
source = "simplisik/pulseback"
version = "~> 0.1"
}
}
}
provider "pulseback" {
api_key = var.pulseback_api_key
workspace_id = var.pulseback_workspace_id
}
In CI, leave the provider block empty and set PULSEBACK_API_KEY and PULSEBACK_WORKSPACE_ID instead. The key acts with the admin role of the admin who created it, and delete it to cut access immediately.
Monitors
A url on its own is an HTTP monitor. Unset arguments use the same defaults as the dashboard: GET, 200-299, checks every 5 minutes from US East, EU West and Asia Pacific, down after 2 confirmations. The name defaults to what’s checked (checkout.acme.dev/health).
resource "pulseback_monitor" "checkout" {
url = "https://checkout.acme.dev/health"
interval = 30
regions = ["us-east", "eu-west", "ap-south"]
confirm = 2
escalation_policy = pulseback_policy.platform.id
}
interval must be at least your plan’s minimum (Free 300 seconds, Starter 60, Team and up 30), and confirm can’t be more than the number of regions. Add keyword to an HTTP monitor to make it a keyword check. Every other check type has its own object, and the type comes from the object you set:
resource "pulseback_monitor" "postgres" {
name = "db-1"
tcp = { host = "db-1.acme.dev", port = 5432 }
}
resource "pulseback_monitor" "certificate" {
ssl = { hostname = "acme.dev", warn_days = 21 }
}
resource "pulseback_monitor" "backup" {
name = "Nightly backup"
heartbeat = { period_seconds = 86400, grace_seconds = 1800 }
}
output "backup_ping_url" {
value = pulseback_monitor.backup.ping_url
}
| Type | Set |
|---|---|
| HTTP | url (plus method, headers, body, expected_status, follow_redirects, degraded_after_ms) |
| Keyword | url and keyword (keyword_mode, case_sensitive) |
| TCP | tcp = { host, port, tls } |
| Ping | ping = { host, port } |
| DNS | dns = { hostname, record_type, expected, resolver } |
| SSL expiry | ssl = { hostname, port, warn_days } |
| Domain expiry | domain = { domain, warn_days } |
| Heartbeat | heartbeat = { period_seconds, grace_seconds }, then call ping_url |
| Cron | cron = { schedule, time_zone, grace_seconds }, then call ping_url |
Changing a monitor’s type replaces it, and heartbeat and cron monitors then get a new ping URL. Everything else updates in place.
On-call: schedules and escalation policies
Look people up by email with the pulseback_member data source. Schedules and multi-step escalation policies need the Team plan or above.
data "pulseback_member" "priya" {
email = "priya@acme.dev"
}
data "pulseback_member" "sam" {
email = "sam@acme.dev"
}
resource "pulseback_schedule" "platform_primary" {
name = "Platform primary"
time_zone = "America/Chicago"
rotation = {
kind = "weekly"
handoff_weekday = 1
handoff_time = "09:00"
start_date = "2026-11-02"
participants = [data.pulseback_member.priya.id, data.pulseback_member.sam.id]
}
}
resource "pulseback_policy" "platform" {
name = "Platform"
step {
schedule_ids = [pulseback_schedule.platform_primary.id]
}
step {
delay_minutes = 5
user_ids = [data.pulseback_member.priya.id]
}
}
Add restriction = { days = [1, 2, 3, 4, 5], start = "09:00", end = "17:00" } to a schedule to keep it to business hours. pulseback_policy is an alias of pulseback_escalation_policy, so use either name. To look up an existing policy, such as the Default one every workspace starts with, use data "pulseback_escalation_policy" with its name.
Status pages and maintenance
resource "pulseback_status_page" "public" {
name = "Acme status"
slug = "acme"
component {
name = "Checkout"
monitor_ids = [pulseback_monitor.checkout.id]
}
component {
name = "Email delivery"
manual_state = "operational"
}
}
resource "pulseback_maintenance" "db_upgrade" {
status_page_id = pulseback_status_page.public.id
title = "Database upgrade"
component_ids = [pulseback_status_page.public.component[0].id]
starts_at = "2026-11-08T02:00:00Z"
ends_at = "2026-11-08T03:00:00Z"
}
Components are matched by name, so reordering them keeps their ids (and any maintenance windows that point at them). Set custom_domain on a status page to serve it from your own domain on Starter and above.
Integrations
Inbound integrations give you an endpoint to paste into the sending system: a routing key for the Events API, an address for email, or a URL for deploy tracking. Terraform marks it sensitive.
resource "pulseback_integration" "prometheus" {
type = "events_api"
name = "Prometheus"
escalation_policy = pulseback_policy.platform.id
}
resource "pulseback_integration" "github" {
type = "github"
name = "acme/api"
}
Slack and outbound webhooks hold secrets that can’t be read back, so set those up in the app.
Importing what you already have
Every resource imports by its id, which you’ll find in the dashboard URL:
terraform import pulseback_monitor.checkout mon_AbC123dEf456gHiJ
terraform import pulseback_maintenance.db_upgrade sp_AbC123dEf456gHiJ/mnt_KlM789nOp012qRsT
Plan limits and drift
- Over a plan limit (monitors, status pages, the check interval, schedules on Free or Starter), the API answers
402 plan_limitand Terraform stops with Pulseback plan limit reached, naming the limit, your plan and the plan that includes it. - Updates send only the settings that changed, so anything you don’t manage in Terraform is left alone.
- If someone changes a managed setting in the dashboard, the next
terraform planshows it and puts it back. That includes settings you left at their defaults. Resources deleted in the app are recreated.