Backup & Restore (Admin)
Export Configuration Backup
GET /api/v1/admin/backup
Export all configuration as a downloadable JSON file. Admin-only.
Response: 200 OK — application/json download with Content-Disposition: attachment; filename="osprey-config-YYYYMMDD-HHMMSS.json"
{
"version": "1.0",
"exported_at": "2026-02-24T12:00:00Z",
"networks": [...],
"autonomous_systems": [...],
"routing_domains": [...],
"protocol_instances": [...],
"areas": [...],
"collector_configs": [...],
"alert_rules": [...],
"notification_channels": [...],
"snmp_targets": [...],
"users": [{"username": "admin", "display_name": "Administrator", "role": "admin", "is_active": true}],
"system_settings": {"topology.stale_retention_hours": "168", ...},
"maintenance_windows": [...]
}
Notes:
- Users are exported WITHOUT password hashes (security).
- API keys are NOT exported (contain sensitive hashes).
- All hierarchy entities include parent name context for import matching.
- Audit log entry created on export.
Error responses:
401 Unauthorized -- Missing or invalid access token
403 Forbidden -- Non-admin user
500 Internal Server Error -- Database query failure
Export Database Dump
GET /api/v1/admin/backup/database
Full PostgreSQL dump via pg_dump --compress=gzip:6. Returns the compressed dump as a streaming download. Admin-only. The response is streamed directly from pg_dump stdout -- no memory buffering on the server regardless of database size. Rate-limited to one database backup per user per minute (429).
The 200 and the download headers are withheld until pg_dump has actually produced its first byte. pg_dump starts successfully and only then exits non-zero for a server-version mismatch, an authentication failure or an unreachable database, so committing the status line earlier delivered those failures as a successful download of an empty .sql.gz. A failure after the first byte cannot change the status code -- the response is already streaming -- and surfaces as a truncated body plus a server-side error log.
Response: 200 OK — application/gzip download with Content-Disposition: attachment; filename="osprey-database-YYYYMMDD-HHMMSS.sql.gz"
Error responses:
401 Unauthorized -- Missing or invalid access token
403 Forbidden -- Non-admin user
429 Too Many Requests -- database backup can only be requested once per minute
500 Internal Server Error -- database export failed: pg_dump produced no output (pg_dump started but exited without writing a dump)
503 Service Unavailable -- pg_dump not available on this system (pg_dump binary not found in PATH)
503 Service Unavailable -- database URL not configured for backup (server not configured with database URL)
List Deleted-Area Tombstones
GET /api/v1/admin/deleted-areas
Admin-only. Returns { "deleted_areas": [ { id, area_label, area_name, protocol, network_name, deleted_at } ] }, newest first — the tombstones every hierarchy delete transaction writes. Topology history keeps dead area UUIDs by design, and these records are the only surviving way to name them.
Purge History of Deleted Areas
DELETE /api/v1/admin/history?area_ids=<uuid>,<uuid>
Admin-only. Deletes topology events, snapshots and incidents for the given dead area UUIDs (typically taken from the tombstone list) — the after-the-fact purge for history retained when an area was deleted without ?delete_history=true. Returns { "purged_areas": n }. 400 on missing/malformed area_ids.
Hierarchy delete semantics: every hierarchy DELETE (area, PI, RD, AS) is refused with 409 when an enabled recorder homed outside the delete set feeds any affected area — the cascade cannot stop that recorder and it would re-create the entity within the hour. The 409 body names the recorder and its home area; ?force=true proceeds anyway (history, layouts and alert scopes will not survive the round-trip). All nested hierarchy routes also verify the parent chain and return 404 for a child addressed under the wrong parent, and every hierarchy PUT now has true patch semantics: only fields present in the body are applied.
Restore Configuration Backup
POST /api/v1/admin/restore
Import configuration from a previously exported JSON backup file. Admin-only.
Body size limit: 10 MB
Request body: The same JSON structure as the GET /api/v1/admin/backup response.
Only backup format version 1.0 is accepted; an unknown version is rejected
instead of being interpreted as the current schema.
Response: 200 OK
{
"created": 5,
"skipped": 12,
"errors": ["area \"0.0.0.1\": parent PI not found"]
}
Response fields:
| Field |
Type |
Description |
created |
integer |
Number of new entities created |
skipped |
integer |
Number of entities skipped (already exist, matched by name) |
errors |
string[] |
List of non-fatal errors encountered during import |
Behavior:
- Runs in a single database transaction.
- Entities matched by name are skipped (not updated).
- New entities are created with new UUIDs.
- Hierarchy FK references resolved via name matching.
- Audit log entry created with created/skipped/error counts.
Error responses:
400 Bad Request -- Invalid JSON, missing required fields, or an unsupported backup version
401 Unauthorized -- Missing or invalid access token
403 Forbidden -- Non-admin user
413 Payload Too Large -- Request body exceeds 10 MB limit
500 Internal Server Error -- Transaction failure (all changes rolled back)
Restore Database
POST /api/v1/admin/restore/database
Restore a full PostgreSQL database from a previously exported .sql or .sql.gz dump file. Admin-only. Osprey first fully decompresses, size-checks, and screens the SQL in bounded chunks into a private disk-backed staging file. No database command runs until validation succeeds; large single-row INSERT/COPY payloads have no separate scanner line limit.
The screener tracks SQL tokens across line boundaries, standard and PostgreSQL escape strings, identifiers, nested comments, dollar-quoted bodies, and COPY ... FROM stdin data. Role/user administration, grants/revokes, LOAD, DO, REASSIGN OWNED, FROM PROGRAM, transaction-control statements (BEGIN/COMMIT/ROLLBACK/SAVEPOINT and aliases), and arbitrary psql meta-commands are rejected; harmless words inside quoted values or COPY data are not treated as statements.
Warning: A successful restore replaces all existing data. The ag_catalog/public schema reset, import, and verification of essential Osprey schema tables run in one PostgreSQL transaction, so prohibited input, malformed uploads, non-Osprey SQL files, and SQL import failures preserve the existing database.
Content-Type: multipart/form-data
Capacity policy: api.max_restore_size is an optional hard ceiling applying to both HTTP upload and decompressed SQL; default 0 disables that arbitrary ceiling. The effective limit follows current disk capacity. api.restore_staging_dir selects the staging filesystem (packaged default /var/lib/osprey/restore), while api.restore_min_free_bytes (default 5 GiB) and api.restore_min_free_percent (default 10) define the larger mandatory reserve. Osprey additionally retains one staged-SQL size for database construction and checks api.restore_database_dir (default /var/lib/postgresql) for local PostgreSQL before import. A custom staging path in the Debian package also requires a matching systemd ReadWritePaths= drop-in because the API unit uses ProtectSystem=strict.
Form fields:
| Field |
Type |
Description |
file |
file |
A .sql or .sql.gz file from GET /admin/backup/database |
Response: 200 OK
{
"status": "ok",
"message": "Database restored from osprey-database-20260306-120000.sql. Services should be restarted."
}
Error responses:
400 Bad Request -- Missing file, unsupported extension, invalid multipart/gzip/SQL input, or prohibited SQL
401 Unauthorized -- Missing or invalid access token
403 Forbidden -- Non-admin user
413 Payload Too Large -- Compressed upload or decompressed SQL exceeds a non-zero api.max_restore_size
507 Insufficient Storage -- Staging or local PostgreSQL capacity cannot retain the configured reserve and database-build headroom
503 Service Unavailable -- psql not available or database URL not configured
500 Internal Server Error -- Staging filesystem inspection failed or the atomic import failed; screened transaction control cannot end the wrapper transaction early