Documentation
Everything you need, on one page
From zero to a verified check in about five minutes. The agents are plain bash: read them before you run them, they're short on purpose.
Quickstart
Create an account and a check
Sign up (email + password, nothing else), then create a check from the dashboard: give it a name, pick your backup tool, and set how often it should report. For a nightly backup, an expected interval of 1 day with a 2 hour grace period is a good default. You'll get a unique ping URL for that check.
Download the agent for your tool
Each agent is a single bash script with no dependencies beyond
curland your backup tool itself. It does not take backups and never sees your data. It verifies an existing repository and reports the verdict.Add one line after your backup command
# your existing job, unchanged: restic backup /srv/data # new: verify the repository, then report to Vestal: RESTIC_REPOSITORY=/path/to/repo \ VESTAL_PING_URL=https://vestalapp.com/ping/<your-token> \ bash vestal-restic.sh
That's it. If verification passes, Vestal records a check-in. If it fails, or the script crashes, or the job never runs at all, you get alerted on every channel you've configured.
Agent: restic
Runs restic check against your repository. Set VESTAL_READ_DATA_SUBSET to also re-read a percentage of actual pack data: slower, but it's the part that catches silent bit-rot that a metadata-only check cannot see. Written against restic 0.17+ conventions. If you haven't restore-tested a restic repo by hand before, this walkthrough covers what actually goes wrong and how to check for it yourself first.
| Variable | Required | Meaning |
|---|---|---|
RESTIC_REPOSITORY | yes | your repository, as restic already expects |
RESTIC_PASSWORD | yes | or RESTIC_PASSWORD_FILE / RESTIC_PASSWORD_COMMAND, as usual |
VESTAL_PING_URL | yes | the check's ping URL from the dashboard |
VESTAL_READ_DATA_SUBSET | no | e.g. 5%, re-read and verify that share of real data each run |
RESTIC_REPOSITORY=sftp:backup@nas:/srv/restic-repo \ RESTIC_PASSWORD_FILE=/root/.restic-pass \ VESTAL_READ_DATA_SUBSET=5% \ VESTAL_PING_URL=https://vestalapp.com/ping/<token> \ bash vestal-restic.sh
Agent: BorgBackup
Runs borg check. Unlike restic, Borg's --verify-data has no percentage knob: it decrypts and CRC-checks every data block or none, so VESTAL_VERIFY_DATA is a plain on/off switch. Written against Borg 1.4.x. See how this compares to restic and Kopia's own verify commands here.
| Variable | Required | Meaning |
|---|---|---|
BORG_REPO | yes | your repository, as Borg already expects |
BORG_PASSPHRASE | yes | or BORG_PASSCOMMAND / BORG_PASSPHRASE_FD |
VESTAL_PING_URL | yes | the check's ping URL |
VESTAL_VERIFY_DATA | no | any non-empty value adds --verify-data (full data verification, all-or-nothing) |
Agent: Kopia
Runs kopia content verify, and with VESTAL_VERIFY_PERCENT set, a full verify that downloads and checksums that share of real data plus kopia snapshot verify. The agent always uses a fresh cache directory: in our own pre-release testing, a warm local cache reported a deliberately-corrupted repository as healthy. That's not a theoretical concern; the agent exists in its current form because of it.
| Variable | Required | Meaning |
|---|---|---|
KOPIA_CONFIG_PATH | yes | path to the kopia config file for the repository connection |
VESTAL_PING_URL | yes | the check's ping URL |
VESTAL_VERIFY_PERCENT | no | e.g. 10, download and checksum that percentage of file data |
Agent: Duplicati
Runs duplicati-cli test with --full-remote-verification and retries disabled. Duplicati's default retry behavior turns real corruption into a multi-minute hang instead of a fast failure. With retries off, a broken backend produces an immediate Hash mismatch and an immediate alert.
| Variable | Required | Meaning |
|---|---|---|
VESTAL_DUPLICATI_TARGET | yes | the backup's storage URL, e.g. file:///mnt/backups/work |
VESTAL_DUPLICATI_PASSPHRASE | yes | the backup's encryption passphrase |
VESTAL_PING_URL | yes | the check's ping URL |
VESTAL_DUPLICATI_CLI | no | path to the CLI if it isn't on $PATH |
VESTAL_VERIFY_SAMPLES | no | how many remote file samples to download and verify (default 1) |
Ping API
Using a tool without a ready-made agent? The ping API is three URLs. The token is a 192-bit bearer secret, so treat the URL like a password.
| Endpoint | Meaning |
|---|---|
GET|POST /ping/<token> | success: verification passed |
GET|POST /ping/<token>/start | a run has begun (optional, enables duration tracking) |
GET|POST /ping/<token>/fail | explicit failure: alerts immediately, no waiting for the deadline |
Optional form/query parameters on any of them: message (short text, e.g. a log tail) and duration_ms. Pings are rate-limited to 30 per minute per token.
# minimal integration for any tool, in two lines: my-backup-tool verify --repo /srv/repo \ && curl -fsS https://vestalapp.com/ping/<token> \ || curl -fsS https://vestalapp.com/ping/<token>/fail
REST API
Everything the dashboard does is available with your API key (shown once at signup, revocable any time): Authorization: Bearer vs_…. Keys are stored server-side only as SHA-256 hashes.
| Endpoint | What it does |
|---|---|
GET /api/checks | list your checks, each with its ping URL |
POST /api/checks | create a check (name, backend, expected_interval_seconds, grace_period_seconds) |
DELETE /api/checks/:id | delete a check and its ping history |
GET /api/alert-channels | list alert channels |
POST /api/alert-channels | add one (kind: discord | slack | email | webhook, target) |
DELETE /api/alert-channels/:id | remove one |
curl -s https://vestalapp.com/api/checks \
-H "Authorization: Bearer vs_your_key" \
-H "Content-Type: application/json" \
-d '{"name":"nas-nightly","backend":"restic","expected_interval_seconds":86400,"grace_period_seconds":7200}' \
-X POSTThe docs end here. The setup takes five minutes.
Set up your first verified check in about five minutes. No card, no trial clock, no sales call.