Integration API
Restow ships a documented REST API under /api/v1, with an OpenAPI description, in every edition from day one: it is not a paid add-on. It exists to connect Restow to the tools an MSP or IT department already runs: RMM, PSA and ticketing systems. See Reference → API reference for where the machine-readable description and the operational endpoints live.
What it covers
Section titled “What it covers”- Backup, restore and verify status, and the underlying jobs (with a server-sent-events stream for live progress).
- Protected objects: mailboxes, OneDrives, IMAP accounts and, since 0.4.0, SharePoint sites (kind
site) and, since 0.4.5, the calendar and conversations of Microsoft 365 groups (kindgroup), with their last snapshot and their status (active,excludedororphaned; since 0.4.7 anorphanedobject may still be at its source, see Contract 1.7.0). - Storage targets and their state.
- Servers and clients backed up by the agent (
GET /api/v1/endpoints), see Servers and clients. - File shares (SMB and NFS) and Proxmox VE guests as counts in
GET /api/v1/status, see File shares and stored bytes. - Archive status and evidence: intake per capture channel (Exchange journaling, Graph, IMAP archiving, file import; forwarded copies count in the totals only, see Archive capture), hash-chain verification, retention and legal holds, and an evidence report for a period as JSON (a signed PDF is not produced; see Archiving).
- The running version and whether an update is available, see Version and updates.
- A per-tenant directory: a clean listing of users and mailboxes, meant for handing to a PSA for billing or asset tracking.
- Webhooks, see below.
API keys and scopes
Section titled “API keys and scopes”Create keys under Integrations → API keys. Each key belongs to one tenant, has a name (shown in the key list and the audit log), an optional expiry (30/90/180/365 days, or none), and only the scopes it needs. Scopes are not implied by each other, so a key that changes protection also needs read access if it reads users too. The full set:
| Scope | Grants |
|---|---|
status:read |
Summary, storage, latest verify report, servers and clients (endpoints), running version. |
jobs:read |
Jobs with progress, failures and duration. |
items:read |
Protected objects with their last snapshot. |
users:read |
The user/mailbox directory, for PSA sync and billing. |
archive:read |
Archive status, capture channels, chain verification and the evidence report. |
restore:write |
Start backups and restores. |
verify:write |
Start verify runs (sampled read-back checks). |
users:write |
Include/exclude users from protection, e.g. on- and offboarding. |
webhooks:manage |
Create, change and delete this tenant’s webhooks. |
A key authenticates as Authorization: Bearer <key> and is rate-limited to 600 requests per 10 minutes. Restow shows the key’s value once, at creation: it stores only a fingerprint, so a lost key can’t be recovered, only revoked and replaced. Revoking is immediate and irreversible; the entry stays listed for the audit trail.
The Service Provider edition additionally offers provider keys: they work across every tenant the provider manages (requests name the tenant with an X-Restow-Tenant header), for a single integration point across all customers. They need the full images with a Service Provider license key: without one, creating a provider key is refused with 403 (urn:restow:problem:edition-required in the full images, urn:restow:problem:feature-unavailable in the Community images).
Servers and clients
Section titled “Servers and clients”GET /api/v1/endpoints lists the servers and clients that the Restow agent backs up (Endpoint backup), for an RMM’s asset list or per-machine billing. It needs the scope status:read, takes an optional profile=server or profile=client, and is not paged: the answer is { "items": [...], "total": n }. Every read is audited like the other reads. Each entry has:
| Field | Meaning |
|---|---|
id, hostname, displayName |
The machine. |
profile |
server or client. |
os, arch |
The system (linux, or darwin for macOS, today) and the CPU architecture (amd64 or arm64). |
agentVersion, status |
The version of the agent, and active or revoked. |
connection |
online when the agent was heard from within 15 minutes, otherwise offline, or never. |
lastSeenAt, lastBackupAt, lastSuccessAt |
The last contact, the end of the last backup run whatever its outcome, and the end of the last backup that produced a snapshot. |
readiness |
state (green, yellow, red, unverified or no_backup), checkedAt and overdue. A machine is green only after a restore test of its newest backup, which reads sample files back, matched every sampled hash. |
attention |
Why the machine needs a look: silent, backup_overdue, last_backup_failed, restore_test_failed, repository_damaged or never_seen. |
createdAt |
When the machine was created in Restow. |
GET /api/v1/status also carries an endpoints object with the counts total, servers, clients, revoked, needingAttention and lastSuccessAt. A machine that was removed from Restow (since 0.4.5, see Uninstall and re-enroll) is in no list and no count. The reason revoke_suggested (no contact for a long time) is shown in the web interface only; the API does not report it, and such a machine is silent or backup_overdue anyway.
File shares and stored bytes
Section titled “File shares and stored bytes”Integration API contract 1.4.0 (since 0.4.0) is additive: nothing was removed or renamed.
GET /api/v1/statushas an objectfileSharesnext toendpointsandguests:total(file shares that are not retired),protected(in an enabled backup job),withoutJob,failedLastBackup,warnings(newest backup ended with warnings nobody acknowledged),lastSuccessAtandrestorePoints. A file share in a backup job also counts inreadinessandrecoveryReadiness.- The readiness summary of
GET /api/v1/verify/latesthassharesWithoutJob: file shares in no backup job that keep a restore point, rated by it, but nothing backs them up. Like machines and guests without a job, any of them makesoverallat least yellow. eventsis optional when a webhook is created, see Webhooks.physicalBytesofGET /api/v1/statusand ofusageinGET /api/v1/storagenow include the restic repositories (since 0.4.1): of machines, file shares and Proxmox VE containers, besides the chunk store. The fields and their types stay. A container’s repository counts from its next backup on, of a machine or file share as before (from its next backup or retention run); until then it counts as zero. The size of a restic repository has no history, so the growth of what is stored comes from the chunk store only.
Contract 1.5.0 (since 0.4.2)
Section titled “Contract 1.5.0 (since 0.4.2)”Integration API contract 1.5.0 is additive: nothing was removed or renamed, and info.version of GET /api/v1/openapi.json says 1.5.0 (the contract version; x-restow-build carries the running build).
storageIntegrity.statecan beunreadable. The objectstorageIntegrityof the report ofGET /api/v1/verify/latest(next tolastCheckedAtandlastFullCheckAt) gives the result of the latest storage check asstate:ok,repaired,unreadable,corruptornever(until one ran).unreadablemeans that pack files could not be read at the moment, because of a transfer or permission error, which is not proven damage; the next storage check reads them again first.corruptstill means packs that are proven damaged. A client that handles the values exhaustively should acceptunreadable, and should treat it as something to look at, not as damage. The rules behind it are under Damaged or temporarily unreadable.- A stopped backup is not a failure. A run of a server or client that someone stopped in the web interface ends
cancelled, a new run and task status for machines. It raises nojob.failedwebhook and no failure alert, and it does not count as the last backup (Endpoint backup). - New routes of the web interface are not part of the contract. Since 0.4.2 the release notes name routes such as
GET /api/v1/storage/destination,POST /api/v1/pve/guests/:id/repository/reset,POST /api/v1/pve/guests/:id/verify,POST /api/v1/pve/verify-unverified,GETandPUT /api/v1/settings/pve,GET /api/v1/snapshots/:id/calendarandGET /api/v1/snapshots/people. They are what the buttons of the web interface call. They need a signed-in session, are not in the OpenAPI description and are not reached by an API key, and they may change with the interface. They are listed under API reference.
Archive capture (since 0.4.4)
Section titled “Archive capture (since 0.4.4)”Contract 1.5.0 is unchanged by the archive capture since 0.4.4 (Graph, IMAP archiving and forwarded copies, see Archiving): no field was added, removed or renamed, and info.version still says 1.5.0. What an integration sees:
GET /api/v1/archive/statusandGET /api/v1/archive/report(scopearchive:read) count forwarded copies in their totals (items,intake, the number and size of the items in a report) but do not list them as a channel of their own yet. The channels stayjournal,graphSync,imapSyncandfileImport, so the channels can add up to less than the total when forwarded copies exist. A client should not assume that they add up. The contract does not name forwarded copies yet; a channel of their own is planned.graphSyncandimapSyncnow carry counts. They were part of the contract from the start but nothing wrote them; since 0.4.4 Graph capture and IMAP archiving do (lastCapturedAtandlast24hper channel).- The chain state in both calls still reads
not_verified, and the evidence report is still JSON only.
The web interface and its session API gain fields and routes that are not part of the contract: archiveCapture on mail jobs, captureKind on their members and candidates, mailSourceKinds in the defaults of a new job (GET /api/v1/backup-jobs/defaults?kind=mail), includeSuperseded on GET /api/v1/archive/search (which hides captures that a stronger way replaced unless it is true) and the routes of the capture status, the hold confirmation and the forwarding addresses. They need a signed-in session and may change with the interface. They are listed under Archive capture (since 0.4.4) in the API reference, and the capture ways of a job under How mail reaches the archive.
Contract 1.6.0 (since 0.4.5)
Section titled “Contract 1.6.0 (since 0.4.5)”Integration API contract 1.6.0 is additive: nothing was removed or renamed, and info.version of GET /api/v1/openapi.json says 1.6.0.
ObjectKindincludesgroup. The calendar and the conversations of a Microsoft 365 group (a team included) are protected objects of their own kind,group, with their own last snapshot (Connecting sources). A client that handles object kinds exhaustively should accept the new value. A group mailbox never counts as a protected mailbox.GET /api/v1/statusreportslastSuccess.groupsnext tomail,onedrive,imap,sharepointandarchive: the completion time of the newest restorable backup of a group mailbox without failed items. It isnullwhile no group is backed up, which is the case after the update until groups are switched on (they are opt-in).version.releasedAtin the same call: the release date of the running build, see Version and updates.- The routes behind the new buttons are not part of the contract. Stopping and starting a whole job (
POST /api/v1/backup-jobs/:id/cancel,POST /api/v1/pve/jobs/:id/run,POST /api/v1/pve/jobs/:id/cancel) and removing a machine (POST /api/v1/endpoints/:id/remove,GET /api/v1/endpoints/removals) are what the web interface calls. They need a signed-in session, are not in the OpenAPI description and are not reached by an API key. They are listed under API reference. - A stopped backup is still not a failure. A backup of a machine, a file share or a Proxmox VE guest that someone stops ends
cancelled, as a single stopped backup has since 0.4.2 (Contract 1.5.0); since 0.4.5 the same holds when a whole job is stopped.
Contract 1.7.0 (since 0.4.7)
Section titled “Contract 1.7.0 (since 0.4.7)”Integration API contract 1.7.0 is additive, and info.version of GET /api/v1/openapi.json says 1.7.0, with one change of meaning that can break a client: the status orphaned.
Everything else is additive:
GET /api/v1/users. The objects of a user also carryinactiveSince(when the object stopped being backed up) andstatusConfirmedAt(when an administrator confirmed that this is intended;nullwhile it still waits). A user’srecoveryReadinessreads at least yellow, never green, while one of its objects waits for that confirmation, and yellow instead ofnullwhen such an object has no backup to rate.toConfirm. The readiness summary ofGET /api/v1/statusandGET /api/v1/verify/latestcounts the objects that are no longer backed up and wait for a confirmation. Any of them makesoverallat least yellow, never red.guests.excluded.GET /api/v1/statusleaves the Proxmox VE guests that an administrator excluded from backup out ofguests.totalandguests.withoutJoband reports their number asguests.excluded(Excluding a guest from backup).- A new skip reason.
POST /api/v1/jobs/backupskips an object with the reasoninactivewhen it is no longer backed up and still at the source (statusReasonset); an object removed from the source is still skipped asorphaned. The other reasons areexcluded,source_pending,source_disabledandalready_queued. A client that handles the reasons exhaustively should acceptinactive. - Webhooks.
job.completedandjob.failedof a completed restore carry its counters indata.job.result(restored,skipped,failures,unverified).report.alertcan name four new events:object.inactive,object.inactive_many,source.app_secret_expiringandinstallation.app_secret_expiring(Notifications and reports). A client that handles event names exhaustively should accept them. - Some values follow new rules. A backup that Microsoft Graph or the storage held back, and a backup that ended neutral for an object that is no longer backed up, raise no
job.failed(Backups held back by a service).objects.failedofGET /api/v1/statusleaves out objects whose latest backup was held back by a service or ended neutral for an object no longer backed up; the run before it counts instead. An object removed from its source is rated without the age of its last backup (report.reasonsofGET /api/v1/verify/latestleaves the age out) and is never overdue, inGET /api/v1/status,/objects,/usersand/verify/latest, as in theverify.completednotification.GET /api/v1/jobskeeps the statusfailedfor a held-back or neutral backup: readfailure.code(graph.throttled,graph.service_unavailable,storage.rate_limitedfor held back;graph.mailbox_inactive,graph.object_locked,config.object_inactivefor neutral) and the object’sstatusReason, and treat them as the object’s state, not as a failure. The contract has no field that sets them apart from a failure yet. - The routes behind the new buttons are not part of the contract. Confirming an object (
POST /api/v1/directory/objects/:id/inactive/confirm,POST /api/v1/directory/objects/inactive/confirm), the expiry date of a source’s own app (PATCH /api/v1/sources/:id/own-app/expiry), excluding a guest (PUT /api/v1/pve/guests/:id/excluded) and the prices (GETandPUT /api/v1/settings/pricing) are what the web interface calls. They need a signed-in session, are not in the OpenAPI description and are not reached by an API key. They are listed under API reference.
Version and updates
Section titled “Version and updates”GET /api/v1/status reports the running version and the opt-in update hint in a version object, so an RMM can mark outdated installations. Its fields: running, commit (the source commit of the running build, when known), latest (the newest release of the channel, when the check ran), updateAvailable (null when that is not known), releaseUrl, updateCheck (disabled, pending, ok or failed), checkedAt, channel (stable or beta), latestTag (as published, for example v1.2.3), publishedAt, checkError (why the last check failed, for example rate_limited or not_found; null when it did not), maintenance (an update that is announced or running through the updater, with phase, targetVersion and startsAt; null otherwise) and, since 0.4.5, releasedAt (when the running build was released, the commit date of its release tag as an ISO-8601 UTC time; null for a build without one). The update check is off until an administrator turns it on, so updateCheck is disabled on a fresh installation. A status call never waits for the network. See Updates.
Why a job failed
Section titled “Why a job failed”Job objects carry the cause of a failure next to the plain errorMessage. failure holds a stable cause code (for example graph.consent_missing, graph.throttled, imap.auth_failed, storage.full, verify.hash_mismatch or unknown), its category, whether waiting alone may help (transient) or a manual retry makes sense (retryable), params (the missing permission, the host, the seconds to wait), redacted technical details for a support case, occurredAt, the step the run was in, the automatic retry state, the ordered steps to fix it and a docsUrl. It is null for a job that did not fail or has no recorded cause. Since 0.4.7 a job with the status failed can carry a cause that is no failure of the backup: held back by a service (graph.throttled, graph.service_unavailable, storage.rate_limited) or neutral for an object that is no longer backed up (graph.mailbox_inactive, graph.object_locked, config.object_inactive); see Contract 1.7.0. itemCauses lists the causes behind the failed items of a finished job, most frequent first (at most three). The job detail adds a failure to each failed item, and failureGroups, the failed items grouped by cause. See Failure explanations.
Webhooks
Section titled “Webhooks”Restow calls a URL you register when a chosen event happens, for example so a PSA opens a ticket when a backup fails. Events currently defined: job.failed, job.completed, verify.completed, plus a manual “send test event”; the signed report.alert and report.summary come from alert and report rules. Each webhook has its own signing secret, shown once at creation (or after Rotate secret): every delivery is a JSON POST, signed as sha256=<HMAC-SHA-256 of the raw body> in the X-Restow-Signature header, with X-Restow-Event, X-Restow-Delivery and X-Restow-Attempt alongside it. Verify the signature (constant-time comparison) before trusting a request. A non-2xx response is retried at 1 min, 5 min, 15 min, 1 h, 3 h, 6 h and 12 h; HTTP 410 ends the delivery immediately. The delivery log keeps every attempt for about 30 days after it finishes, and a delivery can be replayed on demand. Since 0.3.1 (integration API contract 1.3.0) a webhook also has a format: restow is the signed JSON described here and the default, discord, slack and teams send a chat message without a signature. See Notifications and reports.
Which events a webhook gets is its column in the notification matrix (since 0.4.0, Integrations → Notifications). events on creating or updating a webhook still means the subscribed stream events (job.failed, job.completed, verify.completed), and since contract 1.4.0 it is optional on create: a webhook created without it starts with the defaults of its format (a Discord, Slack or Teams webhook gets errors and warnings, a signed restow webhook everything). The names and payloads of the stream are unchanged; backup.succeeded is the matrix’s name for what the stream calls job.completed, an alias and not a rename. The session API (the web interface’s) also returns a webhook’s column as notifyEvents. See the matrix and the stream.
The job.failed payload (data.job) carries the cause as failure: the stable code, whether waiting alone may help (transient) and the params a ticket needs. A failed run of a server or client raises job.failed too, with queue set to endpoint_backup or endpoint_restore and an additional data.endpoint (id, hostname, displayName, profile, os). A run that the agent had to interrupt because it restarted is not a failure and raises no webhook. Neither does a Mail & SaaS backup that Microsoft Graph or the storage held back, or one that ended neutral for an object that is no longer backed up (since 0.4.7). A restore that completed without restoring anything while items failed raises job.failed with data.job.status still completed, and a job that the reconciler ends for good raises it too. Runs of file shares raise job.completed and job.failed with queue set to file-share-backup, file-share-restore or file-share-copy, protectedObjectId null and an additional data.fileShare (id, name, protocol) (since 0.4.0).
Webhook targets in loopback or private networks are refused unless the operator has allowed them (RESTOW_WEBHOOK_ALLOW_PRIVATE); link-local (cloud metadata) and other reserved ranges are always refused, regardless of that setting.
Auditing
Section titled “Auditing”Every read of user or backup data through the API, by a key or by the web interface, is written to the audit log, exactly the same way: who (or which key), when, what, for whom, and from which IP.