---
title: Command-line tool
description: The upheron command: list and check monitors, and keep a workspace's monitors, contacts, status pages and maintenance windows in a file, applied from CI.
---

# Command-line tool

`upheron` lists your monitors and their status, checks a website on the spot, and keeps your workspace in a file: export it, change it, review the difference and apply it, by hand or from CI. It works on every plan, Free included, and uses only the [public API](/docs/api), within your plan and the [rate limits](/docs/rate-limits).

It is one JavaScript file with no dependencies, for [Node.js](https://nodejs.org) 20 or later or [Bun](https://bun.sh), downloaded from this site. The current version is **1.1.0**, and its SHA-256 is:

```text
51f89447f4b8ea938432f977306aaee8881ce0c2d80fe81d7c638835bbe9cfd4
```

## Install

On macOS and Linux, download it, check it against the SHA-256 above and put it on your `PATH`:

```bash
curl -fsSLO https://upheron.com/downloads/upheron \
  && echo "51f89447f4b8ea938432f977306aaee8881ce0c2d80fe81d7c638835bbe9cfd4  upheron" | shasum -a 256 -c \
  && chmod +x upheron && mkdir -p ~/.local/bin && mv upheron ~/.local/bin/
```

Linux without `shasum` has `sha256sum -c` instead. On Windows, download [`upheron`](https://upheron.com/downloads/upheron), compare `Get-FileHash upheron` with the SHA-256 above, and run it as `node upheron`. The checksum is also at [`/downloads/upheron.sha256`](https://upheron.com/downloads/upheron.sha256). To update, download it again; `upheron version` says which version you have.

## Sign in

Create a token in **Settings → API tokens** (see [Authentication](/docs/authentication)): `read` is enough to list, check, export and diff; `apply` needs `write`. Give it to `upheron` in the `UPHERON_API_KEY` environment variable, or save it once:

```bash
upheron login
```

`login` reads the token without showing it, checks it, and saves it in `~/.config/upheron/config.json` (`%APPDATA%\upheron\config.json` on Windows) readable only by you. `upheron` refuses a config file that others can read. `UPHERON_API_KEY` wins over the file; `upheron logout` deletes it.

## Commands

| Command | What it does |
|---|---|
| `upheron monitors` | Every monitor with its status, kind, target and last check. |
| `upheron monitor <name or id>` | One monitor: status, uptime over 24 hours, 7 and 30 days, certificate and domain expiry, open incidents and vulnerability findings. |
| `upheron check <url>` | Checks a public website (or pings a public IPv4 address) now, from UpHeron's server: ten a minute. |
| `upheron export [-o file]` | Writes the workspace's contacts, monitors, status pages and maintenance windows as YAML, or JSON with `-o file.json` or `--format json`. |
| `upheron diff <file>` | Shows what `apply` would change. |
| `upheron apply <file>` | Creates and updates what the file describes. |

Every command takes `--json` for machine-readable output. Exit codes:

| Code | Meaning |
|---|---|
| 0 | Done: no changes (`diff`), the website is up (`check`), applied (`apply`). |
| 1 | `diff` found changes, or `check` found the website down or unreachable. |
| 2 | A mistake in the command or the file, such as an unknown field or a name used twice. |
| 3 | The API refused the request (its message is shown) or could not be reached. |

## Monitoring as code

Start from your workspace as it is:

```bash
upheron export -o upheron.yaml
```

```yaml
version: 1
contacts:
  - name: Ops email
    kind: email
    destination: ops@example.com
monitors:
  - name: Shop
    kind: http
    url: https://shop.example.com
    interval_seconds: 60
    locations: [fra-1, nyc-1]
    keyword: Add to cart
    contacts: [Ops email]
  - name: Nightly backup
    kind: heartbeat
    heartbeat_grace_seconds: 600
    contacts: [Ops email]
status_pages:
  - name: Example status
    slug: example-status
    components: [Shop, { monitor: Nightly backup, display_name: Backups }]
    appearance: { theme: dark }
maintenance_windows:
  - name: Database upgrade
    monitor: Shop
    starts_at: "2026-12-01T02:00:00Z"
    ends_at: "2026-12-01T03:00:00Z"
  - name: Backup window
    starts_at: "2026-12-01T02:00:00+01:00"
    ends_at: "2026-12-01T02:30:00+01:00"
    recurrence: { frequency: daily }
```

Change it, then `upheron diff upheron.yaml` and `upheron apply upheron.yaml`. How the file is read:

- **Names are the keys.** Each item is matched with the one of the same name in the workspace, so names must be unique within a section, in the file and among what the file lists in the workspace. Renaming an item in the file creates a new one. Contacts, monitors and tabs are named in references too: a monitor's `contacts`, `tab` and `escalation_policy`, a status page's `components`, a maintenance window's `monitor` (left out, the window covers every monitor).
- **What is left out is left alone.** A section the file does not have is not managed, and neither is a field an item does not give: export leaves out fields at their default, so if you want a default kept, write it. The fields are those of the [API](/docs/api) (`paused: true` pauses a monitor).
- **Deleting needs `--delete`.** Items of a managed section that the file does not list are shown as deletions but kept; `upheron apply --delete` deletes them. `--dry-run` shows what `apply` would do. Maintenance windows that have ended are never exported, changed or deleted; a repeating one has ended once it has no occurrences left.
- **Write-only fields** are those the API never returns: a contact's `destination`, `token` and `options`, a monitor's `request_headers`, `request_body` and `repository_token`, and check options such as transaction `steps`. They are sent when the item is created; `--write-secrets` sends them again. Keep secrets out of the file in the repository; the export shows a contact's destination only as a `destination_hint`. A contact's kind and destination, and a monitor's kind, cannot change: give the new one another name.
- **A maintenance window's `recurrence`** ([repeating windows](/docs/api#repeating-windows)) is compared by the keys the file gives, so `{ frequency: daily }` matches a rule the API filled in with its interval and time zone; a change sends the file's rule, and the API fills in the rest again. Occurrences skipped or moved in the dashboard are not in the file and stay as they are.
- **`check_options` are sent whole.** Changing one option sends all the options in the file, and options it does not give are removed, so list every option of a check you change, write-only ones included.
- When the file manages status pages, monitors it creates get no status page of their own. New monitors are created with an `Idempotency-Key`, so a retry never makes a second one; `upheron` waits out rate limits and retries what is safe to retry.

JSON files work the same way, and YAML may use the usual block and flow styles, quotes, comments and `|` blocks; anchors and tags are not supported.

## In CI

Review changes to the file in pull requests and apply them when they are merged. In GitHub Actions, with a `write` token saved as the secret `UPHERON_API_KEY`:

```yaml
name: Monitoring
on:
  pull_request:
    paths: [upheron.yaml]
  push:
    branches: [main]
    paths: [upheron.yaml]
jobs:
  upheron:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: Install upheron
        run: |
          curl -fsSLO https://upheron.com/downloads/upheron
          curl -fsSL https://upheron.com/downloads/upheron.sha256 | sha256sum -c
          chmod +x upheron
      - name: Show the changes
        if: github.event_name == 'pull_request'
        run: ./upheron diff upheron.yaml || [ $? -eq 1 ]
        env:
          UPHERON_API_KEY: ${{ secrets.UPHERON_API_KEY }}
      - name: Apply
        if: github.event_name == 'push'
        run: ./upheron apply upheron.yaml
        env:
          UPHERON_API_KEY: ${{ secrets.UPHERON_API_KEY }}
```

To fail a scheduled job when someone changed the workspace by hand, run `upheron diff upheron.yaml`: it exits 1 when the workspace and the file differ. Runners have Node.js; elsewhere, install Node.js 20 or later or Bun first. To pin a version, check the download against its SHA-256 from this page instead of the `.sha256` file: an update then stops the job until you take it.
