The file
Every key is the REST API's own POST /checks field, by name and by rule: nothing is renamed and nothing is added, so a check here, a check in a curl body, and a check in the Terraform provider are written with the same words. name is the key a check is matched on, so it must be unique within the file. Omit regions for every live region. The file is validated in full before anything is sent, and every problem is listed at once.
version: 1
checks:
- name: API health
url: https://api.example.com/health
intervalSeconds: 60
regions: [iad, sjc, fra, nrt]
assertionStatusMin: 200
assertionStatusMax: 299
assertionJsonPath: data.status
assertionJsonOp: equals
assertionJsonValue: ok
- name: Storefront
url: https://shop.example.com/
regions: [iad, fra]
assertionBodyOp: contains
assertionBodyValue: Add to cart
# Minimal: every live region, your plan's fastest interval.
- name: Marketing site
url: https://www.example.com/YAML here is a strict, documented subset the CLI parses itself (the CLI has no dependencies): block mappings and lists, [flow, lists] of scalars, quoted and plain strings, numbers, booleans, # comments. Anchors, tags, block scalars (|/>), flow mappings, and multi-document streams are refused with a line number rather than guessed at. A .json file with the same keys is always accepted.
Plan, then apply
realuptime export -f realuptime.yaml # bootstrap the file from your account
realuptime validate -f realuptime.yaml # parse + validate only, no API call
realuptime apply -f realuptime.yaml --dry-run # plan; exit 2 if anything would change
realuptime apply -f realuptime.yaml # create / update
realuptime apply -f realuptime.yaml --prune --yes # also delete http checks not in the file
realuptime apply -f realuptime.yaml --allow-replace # accept delete+recreate for url/intervalSeconds changes
realuptime apply -f realuptime.yaml --dry-run --json # the plan as JSONapply lists the account's checks, matches each file entry by name, and prints a plan before doing anything. The prefixes are stable: + create, ~ update, ! replace, - delete, = no change, ? a note about something the plan leaves alone.
$ realuptime apply -f realuptime.yaml --dry-run
Plan: 1 to create, 1 to update, 0 to replace, 0 to delete, 1 unchanged.
+ check "API health" (create)
~ check "Storefront" (update)
regions: [iad, sjc, fra, nrt] -> [iad, fra]
assertionBodyOp: (none) -> "contains"
assertionBodyValue: (none) -> "Add to cart"
assertionBodyCaseSensitive: (none) -> true
= check "Marketing site" (no changes)
? check "Legacy": on the account but not in the file (pass --prune to delete it).
? check "Nightly backup": type "heartbeat" is not manageable through the REST API; left alone.
(dry run; nothing applied)
$ echo $?
2What an update can and cannot change in place
The REST API edits regions (PATCH /checks/:id) and the assertion fields (PATCH /checks/:id/assertions, a full replace) in place, so those are ordinary updates. It has no endpoint for changing url or intervalSeconds on an existing check, so a change to either is a replace: delete and recreate, which loses the check's history and id. The plan says so on that row, and apply refuses to do it unless you pass --allow-replace. Nothing is recreated by surprise.
Leaving intervalSeconds out of an entry means "don't manage it" (the account's value stands). Leaving out every assertion field means "no assertions", and an account check that has some will have them cleared, the same way the assertions endpoint itself treats an empty body.
Deleting: only with --prune, only with --yes
An http check on the account that the file doesn't name is reported as a note and left alone. Pass --prune to plan its deletion, and --yes to confirm (there is no interactive prompt; CI can't answer one). Heartbeat, TCP, DNS, SMTP, and multistep checks are never touched: the API can't create them, so the file can't declare them, so apply never deletes them.
Order and failure
Changes apply deletes first, then replaces, then creates, then updates, so allowance is freed before it is consumed. On the first API refusal the run stops, prints the API's own error message and RU-XXXX code exactly as returned, marks the remaining steps skipped, and exits 1. Re-running is safe: the plan is always recomputed from live state, so a second apply picks up where the first stopped, and a run with nothing to do prints Nothing to apply. and exits 0.
Exit codes
| Code | Meaning |
|---|---|
0 | Applied successfully, or nothing to do, or a dry run that found no changes. |
1 | Invalid file, an API refusal, a plan the CLI can't carry out (a name shared with a non-http check, two account checks with one name), or a replace/prune that needed a flag you didn't pass. |
2 | --dry-run found pending changes: the "drift detected" signal. |
- name: Apply monitoring
env:
REALUPTIME_API_KEY: ${{ secrets.REALUPTIME_API_KEY }}
run: |
realuptime validate -f realuptime.yaml
realuptime apply -f realuptime.yaml --dry-run || test $? -eq 2 # 2 = changes pending, fine on a PR
# on main:
# realuptime apply -f realuptime.yamlExporting an existing account
realuptime export -f realuptime.yaml writes your account's http checks in this format (by extension: .json for JSON; without -f it prints to stdout). It round-trips: applying the exported file straight back plans zero changes. Checks of other types are named in a header comment rather than dropped.
What the file can declare today
Version 1 declares checks only, because the REST API can create and edit http checks and nothing else: status pages are read-only through the API, and components, notification channels, and alert rules have no API resource yet. A top-level key for one of those is refused with a message saying exactly that, rather than accepted and ignored. When the API gains those resources, the file gains them under their API field names, same as checks.
Requirements
A read_write API key (REALUPTIME_API_KEY or --api-key), the same tier and rate-limit rules as the REST API itself, and Node 22 for the CLI. The CLI is the @realuptime/cli workspace package described in the API reference.