Skip to content

Terraform Provider Reference

The OpenStatus Terraform provider enables you to manage your monitoring infrastructure programmatically using HashiCorp Terraform. Define monitors, notification channels, and status pages as code — with full support for version control, automated deployments, and IaC workflows.

Key capabilities:

  • Manage HTTP, TCP, and DNS monitors with assertions
  • Configure notification channels (Slack, PagerDuty, email, webhooks, and more)
  • Create and manage status pages with component groups
  • Import existing resources into Terraform state

Declare the provider in your Terraform configuration. Terraform will automatically download it when you run terraform init.

terraform {
required_providers {
openstatus = {
source = "openstatusHQ/openstatus"
version = "~> 0.1"
}
}
}

For the latest version, refer to the Terraform Registry.

provider "openstatus" {
api_token = "YOUR_OPENSTATUS_API_TOKEN" # or set OPENSTATUS_API_TOKEN env var
}
ArgumentTypeRequiredDescription
api_tokenstringYesYour OpenStatus API token. Can also be set via the OPENSTATUS_API_TOKEN environment variable.
base_urlstringNoBase URL for the OpenStatus API. Defaults to https://api.openstatus.dev/rpc.

Manages an HTTP monitor with support for custom headers, request bodies, and response assertions.

Arguments:

ArgumentTypeRequiredDefaultDescription
namestringYesMonitor name (max 256 chars).
urlstringYesURL to monitor (max 2048 chars).
periodicitystringYesCheck frequency: 30s, 1m, 5m, 10m, 30m, 1h.
methodstringNo"GET"HTTP method: GET, POST, HEAD, PUT, PATCH, DELETE, OPTIONS.
bodystringNo""Request body for POST, PUT, PATCH methods.
timeoutnumberNo45000Timeout in milliseconds (0–120000).
degraded_atnumberNocomputedResponse time threshold (ms) after which the monitor is considered degraded.
retrynumberNo3Number of retries on failure (0–10).
follow_redirectsboolNotrueWhether to follow HTTP redirects.
activeboolNofalseWhether the monitor is active.
publicboolNofalseWhether the monitor is visible on your public status page.
descriptionstringNoMonitor description (max 1024 chars).
regionsset(string)NoRegions to monitor from. See Available Regions.

Blocks:

  • headers (max 20) — Custom HTTP headers to include with each request.

    • key (string, required) — Header name.
    • value (string, required) — Header value.
  • status_code_assertions (max 10) — Assert on response status codes.

    • target (number, required) — Expected status code (100–599).
    • comparator (string, required) — One of: eq, neq, gt, gte, lt, lte.
  • body_assertions (max 10) — Assert on response body content.

    • target (string, required) — Expected value.
    • comparator (string, required) — One of: contains, not_contains, eq, neq, empty, not_empty, gt, gte, lt, lte.
  • header_assertions (max 10) — Assert on response headers.

    • key (string, required) — Header name to assert on.
    • target (string, required) — Expected value.
    • comparator (string, required) — Same comparators as body_assertions.

Read-only attributes: id (string), status (string: active, degraded, error, unknown).

Example — Basic health check:

resource "openstatus_http_monitor" "website" {
name = "Website Availability"
url = "https://www.example.com"
periodicity = "1m"
active = true
public = true
regions = ["fly-iad", "fly-ams", "fly-syd"]
}

Example — API monitor with assertions and headers:

resource "openstatus_http_monitor" "api" {
name = "API Health Check"
description = "Monitors the /health endpoint with full assertions."
url = "https://api.example.com/health"
periodicity = "5m"
method = "GET"
timeout = 30000
active = true
regions = ["fly-iad", "fly-ams", "fly-nrt"]
headers {
key = "Authorization"
value = "Bearer ${var.api_token}"
}
headers {
key = "Accept"
value = "application/json"
}
status_code_assertions {
target = 200
comparator = "eq"
}
body_assertions {
target = "ok"
comparator = "contains"
}
header_assertions {
key = "Content-Type"
target = "application/json"
comparator = "contains"
}
}

Example — POST monitor with request body:

resource "openstatus_http_monitor" "webhook" {
name = "Webhook Endpoint"
url = "https://api.example.com/webhooks/health"
periodicity = "10m"
method = "POST"
active = true
regions = ["fly-iad"]
headers {
key = "Content-Type"
value = "application/json"
}
body = jsonencode({
type = "health_check"
})
status_code_assertions {
target = 202
comparator = "eq"
}
}

Import:

Terminal window
terraform import openstatus_http_monitor.website <monitor_id>

Manages a TCP connection monitor to verify that a port is open and reachable.

Arguments:

ArgumentTypeRequiredDefaultDescription
namestringYesMonitor name (max 256 chars).
uristringYesTarget in host:port format (max 2048 chars).
periodicitystringYesCheck frequency: 30s, 1m, 5m, 10m, 30m, 1h.
timeoutnumberNo45000Timeout in milliseconds (0–120000).
degraded_atnumberNocomputedDegradation threshold in milliseconds.
retrynumberNo3Number of retries on failure (0–10).
activeboolNofalseWhether the monitor is active.
publicboolNofalseWhether the monitor is publicly visible.
descriptionstringNoMonitor description (max 1024 chars).
regionsset(string)NoRegions to monitor from. See Available Regions.

Read-only attributes: id (string), status (string).

Example — Database port check:

resource "openstatus_tcp_monitor" "database" {
name = "PostgreSQL Port Check"
description = "Ensures the database port is open and reachable."
uri = "db.example.com:5432"
periodicity = "1m"
timeout = 10000
active = true
regions = ["fly-iad", "fly-fra"]
}

Example — Redis monitor:

resource "openstatus_tcp_monitor" "redis" {
name = "Redis Connection"
uri = "redis.example.com:6379"
periodicity = "30s"
active = true
regions = ["fly-iad", "fly-ams", "fly-nrt"]
}

Import:

Terminal window
terraform import openstatus_tcp_monitor.database <monitor_id>

Manages a DNS monitor with support for record type assertions.

Arguments:

ArgumentTypeRequiredDefaultDescription
namestringYesMonitor name (max 256 chars).
uristringYesDomain name to monitor (max 2048 chars).
periodicitystringYesCheck frequency: 30s, 1m, 5m, 10m, 30m, 1h.
timeoutnumberNo45000Timeout in milliseconds (0–120000).
degraded_atnumberNocomputedDegradation threshold in milliseconds.
retrynumberNo3Number of retries on failure (0–10).
activeboolNofalseWhether the monitor is active.
publicboolNofalseWhether the monitor is publicly visible.
descriptionstringNoMonitor description (max 1024 chars).
regionsset(string)NoRegions to monitor from. See Available Regions.

Blocks:

  • record_assertions (max 10) — Assert on DNS record values.
    • record (string, required) — DNS record type: A, AAAA, CNAME, MX, TXT.
    • target (string, required) — Expected value.
    • comparator (string, required) — One of: eq, neq, contains, not_contains.

Read-only attributes: id (string), status (string).

Example — A record validation:

resource "openstatus_dns_monitor" "main_domain" {
name = "DNS A Record Check"
description = "Verifies that example.com resolves to the correct IP."
uri = "example.com"
periodicity = "10m"
active = true
regions = ["fly-iad", "fly-ams"]
record_assertions {
record = "A"
comparator = "eq"
target = "93.184.216.34"
}
}

Example — MX record validation:

resource "openstatus_dns_monitor" "email" {
name = "Email MX Record Check"
uri = "example.com"
periodicity = "30m"
active = true
regions = ["fly-iad"]
record_assertions {
record = "MX"
comparator = "contains"
target = "mail.example.com"
}
}

Import:

Terminal window
terraform import openstatus_dns_monitor.main_domain <monitor_id>

Manages a notification channel. Supports 12 provider types: Discord, Email, Slack, PagerDuty, OpsGenie, Webhook, Telegram, SMS, WhatsApp, Google Chat, Grafana OnCall, and ntfy.

Arguments:

ArgumentTypeRequiredDescription
namestringNoNotification channel name.
provider_typestringYesProvider type (see supported values below).
monitor_idsset(string)NoSet of monitor IDs to associate with this notification.

Supported provider_type values: discord, email, slack, pagerduty, opsgenie, webhook, telegram, sms, whatsapp, google_chat, grafana_oncall, ntfy.

Provider-specific blocks — use exactly one block matching your provider_type:

BlockArguments
discordwebhook_url (string, required, sensitive)
emailemail (string, required)
slackwebhook_url (string, required, sensitive)
pagerdutyintegration_key (string, required, sensitive)
opsgenieapi_key (string, required, sensitive), region (string, required: us or eu)
webhookendpoint (string, required), headers (optional list of key/value objects)
telegramchat_id (string, required)
smsphone_number (string, required)
whatsappphone_number (string, required)
google_chatwebhook_url (string, required, sensitive)
grafana_oncallwebhook_url (string, required, sensitive)
ntfytopic (string, required), server_url (string, optional), token (string, optional, sensitive)

Read-only attributes: id (string), created_at (string), updated_at (string).

Example — Slack notification:

resource "openstatus_notification" "slack_alerts" {
name = "Slack Alerts"
provider_type = "slack"
monitor_ids = [openstatus_http_monitor.api.id]
slack {
webhook_url = var.slack_webhook_url
}
}

Example — PagerDuty notification:

resource "openstatus_notification" "pagerduty" {
name = "PagerDuty Escalation"
provider_type = "pagerduty"
monitor_ids = [
openstatus_http_monitor.api.id,
openstatus_tcp_monitor.database.id,
]
pagerduty {
integration_key = var.pagerduty_key
}
}

Example — Email notification:

resource "openstatus_notification" "email" {
name = "On-Call Email"
provider_type = "email"
monitor_ids = [openstatus_http_monitor.api.id]
email {
email = "oncall@example.com"
}
}

Example — Custom webhook:

resource "openstatus_notification" "custom_webhook" {
name = "Custom Webhook"
provider_type = "webhook"
webhook {
endpoint = "https://api.example.com/alerts"
headers {
key = "Authorization"
value = "Bearer ${var.webhook_token}"
}
headers {
key = "Content-Type"
value = "application/json"
}
}
}

Example — Discord notification:

resource "openstatus_notification" "discord" {
name = "Discord Alerts"
provider_type = "discord"
discord {
webhook_url = var.discord_webhook_url
}
}

Import:

Terminal window
terraform import openstatus_notification.slack_alerts <notification_id>

Manages a status page with access control and branding.

Arguments:

ArgumentTypeRequiredDescription
titlestringYesPage title (1–256 chars).
slugstringYesURL slug (1–256 chars). Used in the status page URL.
descriptionstringNoPage description (max 1024 chars).
homepage_urlstringNoLink to your homepage.
contact_urlstringNoLink to your contact page.
iconstringNoURL of the icon to display.
custom_domainstringNoCustom domain (DNS must point to OpenStatus first).
access_typestringNoAccess control: public, password, or email-domain.
passwordstringNoRequired when access_type is password. Sensitive.
auth_email_domainslist(string)NoRequired when access_type is email-domain.

Read-only attributes: id (string), published (bool), theme (string: system, light, dark), created_at (string), updated_at (string).

Example — Public status page:

resource "openstatus_status_page" "main" {
title = "Example Inc. Status"
slug = "example-status"
description = "Real-time status for all Example Inc. services."
homepage_url = "https://example.com"
contact_url = "https://example.com/support"
}

Example — Password-protected status page:

resource "openstatus_status_page" "internal" {
title = "Internal Status"
slug = "internal-status"
description = "Status page for internal services."
access_type = "password"
password = var.status_page_password
}

Example — Email-domain restricted status page:

resource "openstatus_status_page" "company" {
title = "Company Status"
slug = "company-status"
access_type = "email-domain"
auth_email_domains = ["example.com", "subsidiary.com"]
}

Import:

Terminal window
terraform import openstatus_status_page.main <page_id>

Manages a component on a status page. Components can be linked to a monitor or be static.

Arguments:

ArgumentTypeRequiredDescription
page_idstringYesStatus page ID this component belongs to. Forces replacement if changed.
typestringYesComponent type: monitor or static. Forces replacement if changed.
monitor_idstringNoRequired when type is monitor. The monitor ID to display.
namestringNoComponent display name (max 256 chars).
descriptionstringNoComponent description (max 1024 chars).
ordernumberNoDisplay order on the status page.
group_idstringNoComponent group ID this belongs to.
group_ordernumberNoDisplay order within its group.

Read-only attributes: id (string), created_at (string), updated_at (string).

Example — Monitor component:

resource "openstatus_status_page_component" "api" {
page_id = openstatus_status_page.main.id
type = "monitor"
monitor_id = openstatus_http_monitor.api.id
name = "API"
order = 1
}

Example — Static component:

resource "openstatus_status_page_component" "third_party" {
page_id = openstatus_status_page.main.id
type = "static"
name = "Third-party Services"
description = "Status of external dependencies."
order = 2
}

Import:

Terminal window
terraform import openstatus_status_page_component.api <page_id>/<component_id>

Manages a component group on a status page, allowing you to organize components visually.

Arguments:

ArgumentTypeRequiredDescription
page_idstringYesStatus page ID this group belongs to. Forces replacement if changed.
namestringYesGroup name (1–256 chars).

Read-only attributes: id (string), created_at (string), updated_at (string).

Example:

resource "openstatus_status_page_component_group" "infrastructure" {
page_id = openstatus_status_page.main.id
name = "Infrastructure"
}
resource "openstatus_status_page_component_group" "applications" {
page_id = openstatus_status_page.main.id
name = "Applications"
}

Import:

Terminal window
terraform import openstatus_status_page_component_group.infrastructure <page_id>/<group_id>

Look up a single monitor by ID. Works for HTTP, TCP, and DNS monitors.

data "openstatus_monitor" "existing" {
id = "123"
}
output "monitor_name" {
value = data.openstatus_monitor.existing.name
}
output "monitor_type" {
value = data.openstatus_monitor.existing.type
}

Computed attributes: type (http, tcp, dns), name, url (HTTP only), uri (TCP/DNS only), periodicity, method (HTTP only), active, public, description, timeout, status.


List all monitors with pagination.

data "openstatus_monitors" "all" {
limit = 100
offset = 0
}
output "total_monitors" {
value = length(data.openstatus_monitors.all.monitors)
}
ArgumentTypeRequiredDefaultDescription
limitnumberNo50Max results (1–100).
offsetnumberNo0Pagination offset.

Computed: monitors — list of objects with id, name, type.


Look up a notification channel by ID.

data "openstatus_notification" "existing" {
id = "456"
}
output "notification_provider" {
value = data.openstatus_notification.existing.provider_type
}

Computed attributes: name, provider_type, monitor_ids, created_at, updated_at.


Look up a status page by ID.

data "openstatus_status_page" "existing" {
id = "789"
}
output "status_page_url" {
value = data.openstatus_status_page.existing.slug
}

Computed attributes: title, slug, description, homepage_url, contact_url, icon, custom_domain, published, access_type, password (sensitive), auth_email_domains, theme, created_at, updated_at.


Monitors can run from any of the following 28 regions:

Fly.io: fly-ams, fly-arn, fly-bom, fly-cdg, fly-dfw, fly-ewr, fly-fra, fly-gru, fly-iad, fly-jnb, fly-lax, fly-lhr, fly-nrt, fly-ord, fly-sjc, fly-sin, fly-syd, fly-yyz

Koyeb: koyeb-fra, koyeb-par, koyeb-sfo, koyeb-sin, koyeb-tyo, koyeb-was

Railway: railway-us-west2, railway-us-east4, railway-europe-west4, railway-asia-southeast1


This example sets up a complete monitoring stack: HTTP and TCP monitors, Slack notifications, and a public status page with grouped components.

terraform {
required_providers {
openstatus = {
source = "openstatusHQ/openstatus"
version = "~> 0.1"
}
}
}
provider "openstatus" {
api_token = var.openstatus_api_token
}
# --- Variables ---
variable "openstatus_api_token" {
type = string
sensitive = true
}
variable "slack_webhook_url" {
type = string
sensitive = true
}
# --- Monitors ---
resource "openstatus_http_monitor" "api" {
name = "API Health"
description = "Monitors the main API health endpoint."
url = "https://api.example.com/health"
periodicity = "5m"
method = "GET"
timeout = 30000
active = true
public = true
regions = ["fly-iad", "fly-ams", "fly-nrt"]
status_code_assertions {
target = 200
comparator = "eq"
}
body_assertions {
target = "ok"
comparator = "contains"
}
}
resource "openstatus_http_monitor" "website" {
name = "Website"
url = "https://www.example.com"
periodicity = "1m"
active = true
public = true
regions = ["fly-iad", "fly-ams", "fly-syd"]
status_code_assertions {
target = 200
comparator = "eq"
}
}
resource "openstatus_tcp_monitor" "database" {
name = "PostgreSQL"
uri = "db.example.com:5432"
periodicity = "1m"
timeout = 10000
active = true
regions = ["fly-iad"]
}
resource "openstatus_dns_monitor" "domain" {
name = "DNS Resolution"
uri = "example.com"
periodicity = "10m"
active = true
regions = ["fly-iad", "fly-ams"]
record_assertions {
record = "A"
comparator = "eq"
target = "93.184.216.34"
}
}
# --- Notifications ---
resource "openstatus_notification" "slack" {
name = "Slack Alerts"
provider_type = "slack"
monitor_ids = [
openstatus_http_monitor.api.id,
openstatus_http_monitor.website.id,
openstatus_tcp_monitor.database.id,
]
slack {
webhook_url = var.slack_webhook_url
}
}
# --- Status Page ---
resource "openstatus_status_page" "main" {
title = "Example Inc. Status"
slug = "example-status"
description = "Real-time status for all Example Inc. services."
homepage_url = "https://example.com"
contact_url = "https://example.com/support"
}
resource "openstatus_status_page_component_group" "web" {
page_id = openstatus_status_page.main.id
name = "Web Services"
}
resource "openstatus_status_page_component_group" "infra" {
page_id = openstatus_status_page.main.id
name = "Infrastructure"
}
resource "openstatus_status_page_component" "api_component" {
page_id = openstatus_status_page.main.id
type = "monitor"
monitor_id = openstatus_http_monitor.api.id
name = "API"
group_id = openstatus_status_page_component_group.web.id
order = 1
group_order = 1
}
resource "openstatus_status_page_component" "website_component" {
page_id = openstatus_status_page.main.id
type = "monitor"
monitor_id = openstatus_http_monitor.website.id
name = "Website"
group_id = openstatus_status_page_component_group.web.id
order = 1
group_order = 2
}
resource "openstatus_status_page_component" "db_component" {
page_id = openstatus_status_page.main.id
type = "monitor"
monitor_id = openstatus_tcp_monitor.database.id
name = "Database"
group_id = openstatus_status_page_component_group.infra.id
order = 2
group_order = 1
}