Venue backups
A backup is a venue’s configuration captured and kept. It is the durable sibling of a venue-copy snapshot: the same capture, but retained indefinitely, encrypted at rest, and restorable.
Backups ship with the Copy feature — enable that under Features in the
dashboard and the backup_* tools appear.
Backup or snapshot?
Section titled “Backup or snapshot?”They capture the same thing. What differs is what happens to it afterwards.
Snapshot (venue_extract) | Backup (backup_create) | |
|---|---|---|
| Retention | 30 days, then swept | Indefinite — nothing expires it |
| How it goes away | Ages out, or venue_snapshot_delete | Only backup_delete |
| Per user | 20 | 50 |
| Encrypted at rest | Yes | Yes |
| Purpose | The working input to a copy | The artifact you keep |
Use a snapshot to do something now. Use a backup to be able to do something later.
Both capture paths end in the same sealed artifact, and a restore from it can target the venue it came from, a brand-new venue, or a different tenant.
Taking one
Section titled “Taking one”There are two ways in, and the cheap one is worth knowing.
-
Capture a venue directly. Pass
venueId, optionallyconnectionanddomains. This runs the same extract a copy does, so it is chunked for the 30-second run budget: if it needs more calls it returns{ status: "building", snapshotId, domainsRemaining }and must be called again with thatsnapshotId. It seals itself once the extract completes. Passlabelandnoteon the final call. The backup cap is checked before anything is extracted. The working snapshot it builds counts against your 20 snapshots (snapshot_limit_reachedat the cap) and is left behind after sealing —venue_snapshot_deletefrees its slot. -
Or seal a snapshot you already have. Pass the
snapshotIdof a completedvenue_extractinstead. Nothing is re-read from R1, so this is cheap — if you have just extracted a venue in order to copy it, sealing that same snapshot costs one call.
backup_create returns { id, venueId, venueName, label, note, domains, domainsIncomplete, objectCounts, plaintextBytes, encrypted, createdAt }.
The tools
Section titled “The tools”| Tool | What it does |
|---|---|
backup_create | Capture and seal a venue, from a venueId or a completed snapshotId |
backup_list | Your backups, newest first — venue, label, source tenant, domains, counts, size. Filter by venueId or connection |
backup_get | One backup’s metadata |
backup_restore | Compile a restore into a reviewable MOP |
backup_delete | Delete the D1 row and its encrypted blob — irreversible |
The config blob is never returned
Section titled “The config blob is never returned”No tool returns a backup’s configuration, and there is deliberately no export
path. backup_list and backup_get return metadata only — venue, label,
captured domains, per-domain object counts, sizes, timestamps.
The reason is what a venue config contains: DPSK passphrases and RADIUS secrets. The blob is encrypted at rest with the Worker’s key and only ever decrypted server-side, to diff it while compiling a restore.
Restoring
Section titled “Restoring”backup_restore does not run anything. It diffs the stored config against a
target and compiles exactly the same reviewable apply-MOP that
venue_copy_plan produces — which means the review, arm,
verify, and undo steps are identical, and so are the limits.
Pass backupId and exactly one of:
target.venueId— restore into an existing venue, including the one it came from. This is restore-in-place.target.newVenue.{name, address}— rebuild it as a fresh venue.
Optionally targetConnection (a different saved connection makes it a
cross-tenant restore, recreating shared objects in that tenant exactly as a
cross-tenant copy does), domains to restore a subset — just the WLANs, say —
plus collisionPolicy, sharedCollision, acceptWarnings, and mode.
connection and target.connection are accepted as aliases for
targetConnection; two that name different connections are refused with
ambiguous_target_connection, and an unknown key is an error rather than being
ignored.
Then the familiar cycle:
-
Review with
mop_replay { mopId, dryRun: true }. -
Run it with
mop_replay { mopId, armWrites: true }— arming names the target tenant before any write executes. -
Confirm with
venue_copy_verify { backupId, targetVenueId }. -
Reverse it with
venue_copy_undo { runId }if you need to — within 7 days of the run; after that the run record is gone and undo returnsrun_not_found.
What a restore cannot bring back
Section titled “What a restore cannot bring back”Everything a copy cannot carry, a restore cannot either — with one addition that catches people out:
- Masked write-only secrets were never captured. Where R1 masked a secret
on read — a RADIUS or AAA shared secret, a bind password, the guest-portal
external key, a social-login app secret, or a DPSK passphrase — it comes back
as an
operatorInputRequiredbinding you supply at run start; the run refuses withmissing_capturerather than restoring a venue with silently missing secrets. Secrets the source returned in the clear were captured and restore verbatim. - Physical devices are never restored. Switch inventory and AP serial membership are not part of a backup in any mode.
- A partial backup restores partially — see its
domainsIncomplete.
If a restore can’t load the backup: backup_not_found means no such backup for
you; backup_blob_missing means the row exists but its encrypted blob is gone
(delete the row); backup_unreadable means the blob could not be decrypted with
the deployment’s current key — the backup is intact, so don’t delete it.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Backups per user | 50 — backup_limit_reached, and nothing frees itself |
| Retention | Indefinite; backup_delete is the only removal |
| Size | 5 MB of captured config; over it returns backup_too_large — back up fewer domains separately |