September 2026

The Pathly Terraform provider, in practice

APIBehind the scenes

It is published on the registry. Here are the files you actually write: the smallest one worth having, a whole estate described as data, and taking over monitoring that was created by hand.

Installing

The provider is called pathlyhq/pathly on the public registry. There is nothing to compile and nothing to drop into a mirror: declaring the version is enough, and the init command downloads it. The ~> 0.1 constraint accepts patches and refuses a future 0.2, which is the right setting while the schema is not frozen by a 1.0.

terraform {
  required_version = ">= 1.6"
  required_providers {
    pathly = {
      source  = "pathlyhq/pathly"
      version = "~> 0.1"
    }
  }
}

# Aucun argument : la clé vient de PATHLY_API_TOKEN.
provider "pathly" {}

The provider block is empty, and that is not carelessness. The key is read from the PATHLY_API_TOKEN environment variable. Passed through a Terraform variable, it would end up in plaintext in the state file, which one always forgets to encrypt until after pushing it somewhere. The provider also refuses to speak http to anything but localhost: the key travels in an authorisation header, and would be readable by every intermediary.

The smallest file worth having

One scenario, one address, one interval, and a piece of text expected on the page. That last point is the difference between watching a server and watching a site: an application error page answers HTTP 200 perfectly well, and only the expected text exposes it.

resource "pathly_scenario" "site" {
  name         = "Home page"
  url          = "https://shop.example.com"
  interval_sec = 300
  expect_text  = "Our products"
  tags         = ["prod"]
}

An estate described as data

Past three or four journeys, one block per resource becomes a list you re-read on every addition. Describing them in a variable and looping over it changes the nature of the file: adding a journey becomes one line of data, and the threshold, folder and tag rules stay the same for all of them without anyone thinking about it.

resource "pathly_scenario" "journey" {
  for_each = var.journeys

  name         = each.value.name
  url          = each.value.url
  interval_sec = each.value.interval_sec
  expect_text  = each.value.expect_text

  # Un seuil de latence par parcours : le paiement tolère
  # moins qu'une page d'accueil servie depuis le cache.
  max_latency_ms  = each.value.max_latency_ms
  expected_status = 200
  severity        = each.value.severity
  tags            = concat(["terraform", var.environment], each.value.tags)
}

# Un objectif de disponibilité par parcours critique, qui
# alerte à 80 % du budget d'erreur dépensé : au-delà, il
# reste trop peu de marge pour le reste du mois.
resource "pathly_sla_target" "objective" {
  for_each = {
    for key, j in var.journeys : key => j if j.severity == "critical"
  }

  scenario_id          = pathly_scenario.journey[each.key].id
  objective_pct        = 99.9
  window_days          = 30
  exclude_maintenance  = true
  warn_at_budget_ratio = 0.8
}

The availability objective excludes maintenance windows, which only makes sense if those windows also exist in the code. A maintenance declared on one side and an objective ignoring it on the other produce an error budget spent by an announced outage, that is, an alert punishing a planned operation.

# Sauvegarde hebdomadaire : les échecs dans cette fenêtre
# ne dépensent pas le budget d'erreur.
resource "pathly_maintenance_window" "backup" {
  weekday      = 7 # dimanche
  start_minute = 3 * 60
  duration_min = 120
  reason       = "Weekly backup"
}

Spotting what was created by hand

This is the data source’s most useful role, and the one people think of least. It re-reads the inventory as the API sees it. Compared with what the code declares, the gap names the scenarios created in the console and missing from the files: the ones nobody reviews, and nobody will remove when an environment is torn down.

data "pathly_scenarios" "prod" {
  filter_tag = var.environment
  depends_on = [pathly_scenario.journey]
}

output "scenarios_outside_terraform" {
  value = [
    for s in data.pathly_scenarios.prod.scenarios : s.name
    if !contains([for j in pathly_scenario.journey : j.id], s.id)
  ]
}

The same reasoning applies to values the API never returns. A webhook destination is written and never read back: instead, a twelve-character fingerprint, which changes as soon as the address changes. It does not allow reconstructing the destination, and is enough to see on a plan that it was modified elsewhere.

output "webhook_fingerprint" {
  value = pathly_webhook.alerts.url_fingerprint
}

Taking over existing monitoring

Nobody rebuilds their estate to move to code, and the provider does not ask for it. Each resource is imported by its identifier, the one carried by the scenario’s address in the console. After the import, the plan shows the gap between what exists and what the file describes: as long as it is not empty, applying will modify the scenario in place rather than create a second one.

terraform import pathly_scenario.checkout mon_01H8ZK…

The criterion that decides whether any of this is usable fits in one sentence: after an apply, the next plan must be empty. A provider proposing a change on every pass teaches its users very quickly to stop reading its plans, and the day the plan says something true, nobody sees it. That is the provider’s least visible work, and the one its tests insist on most.

What the provider will never do

These absences are not late features. A provider covering everything ends up requiring a key that can do everything, and that key then sits in the variables of a pipeline read by more people than one thinks. What is missing here is missing on purpose, and is done in the interface, by an identified human.

See what it does on your own site

Free audit in 30 seconds — or record a journey and watch it daily.