Complete REST API documentation for the Dynamight backup management system.
Base URL: /api
Authentication: Most endpoints require a valid JWT token sent as an HttpOnly cookie named token. WebSocket endpoints accept the token as a query parameter.
- Authentication
- Two-Factor Authentication (TOTP)
- Jobs
- Schedules
- Runs & Logs
- Credentials
- Providers
- System
- Settings
- WebSocket
- Error Responses
Check if initial setup (admin account creation) is needed.
GET /auth/setup-required
Authentication: None
Response:
{
"setup_required": true
}Create the initial admin account. Only works when no users exist.
POST /auth/setup
Authentication: None
Request Body:
{
"username": "admin",
"password": "securepassword123"
}Response:
{
"id": 1,
"username": "admin"
}Sets token cookie on success.
Authenticate with username and password.
POST /auth/login
Authentication: None
Request Body:
{
"username": "admin",
"password": "securepassword123"
}Response (no 2FA):
{
"id": 1,
"username": "admin"
}Response (2FA enabled):
{
"totp_required": true,
"totp_token": "temporary-token-for-totp-validation"
}Sets token cookie on success (if no 2FA).
End the current session.
POST /auth/logout
Authentication: Required
Response:
{
"message": "Logged out"
}Clears the token cookie.
Get the authenticated user's information.
GET /auth/me
Authentication: Required
Response:
{
"id": 1,
"username": "admin",
"totp_enabled": true
}Get the current JWT token (for WebSocket authentication).
GET /auth/token
Authentication: Required
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs..."
}Change the current user's password.
POST /auth/change-password
Authentication: Required
Request Body:
{
"current_password": "oldpassword",
"new_password": "newpassword123"
}Response:
{
"message": "Password changed"
}Check if 2FA is enabled for the current user.
GET /auth/totp/status
Authentication: Required
Response:
{
"enabled": false
}Generate a new TOTP secret and QR code.
POST /auth/totp/setup
Authentication: Required
Response:
{
"secret": "JBSWY3DPEHPK3PXP",
"qr_code": "data:image/png;base64,iVBORw0KGgo...",
"otpauth_url": "otpauth://totp/Dynamight:admin?secret=JBSWY3DPEHPK3PXP&issuer=Dynamight"
}Enable 2FA after verifying a TOTP code.
POST /auth/totp/enable
Authentication: Required
Request Body:
{
"secret": "JBSWY3DPEHPK3PXP",
"code": "123456"
}Response:
{
"enabled": true,
"recovery_codes": [
"ABCD-1234-EFGH",
"IJKL-5678-MNOP",
"QRST-9012-UVWX",
"YZAB-3456-CDEF",
"GHIJ-7890-KLMN"
]
}Disable 2FA for the current user.
POST /auth/totp/disable
Authentication: Required
Request Body:
{
"code": "123456"
}Response:
{
"enabled": false
}Complete login by validating TOTP code (when 2FA is enabled).
POST /auth/totp/validate
Authentication: None (uses totp_token)
Request Body:
{
"totp_token": "temporary-token-from-login",
"code": "123456"
}Response:
{
"id": 1,
"username": "admin"
}Sets token cookie on success.
Complete login using a recovery code (when 2FA is enabled).
POST /auth/totp/recovery
Authentication: None (uses totp_token)
Request Body:
{
"totp_token": "temporary-token-from-login",
"recovery_code": "ABCD-1234-EFGH"
}Response:
{
"id": 1,
"username": "admin",
"remaining_codes": 4
}Sets token cookie on success.
Get all backup jobs.
GET /jobs
Authentication: Required
Response:
[
{
"id": 1,
"name": "Daily Backup",
"description": "Backup home directory",
"enabled": true,
"source_dirs": ["/home/user/documents", "/home/user/photos"],
"destination_type": "local",
"destination_config": {
"mount_point": "/mnt/backup",
"backup_subdir": "daily",
"usb_uuid": "1234-5678",
"auto_mount": true,
"auto_unmount": true
},
"sync_options": {
"delete_extraneous": false,
"exclude_patterns": ["*.tmp", ".cache"],
"bandwidth_limit_kbps": null,
"dry_run": false,
"verbosity": "normal"
},
"credential_id": null,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z",
"last_run": {
"id": 5,
"status": "completed",
"started_at": "2026-01-05T02:00:00Z",
"completed_at": "2026-01-05T02:15:00Z"
},
"schedules": [
{
"id": 1,
"schedule_type": "daily",
"hour": 2,
"minute": 0
}
]
}
]Get a single job by ID.
GET /jobs/:id
Authentication: Required
Response: Same as single item in list response.
Create a new backup job.
POST /jobs
Authentication: Required
Request Body:
{
"name": "S3 Backup",
"description": "Backup to AWS S3",
"enabled": true,
"source_dirs": ["/home/user/documents"],
"destination_type": "s3",
"destination_config": {
"bucket": "my-backup-bucket",
"prefix": "backups/server1/",
"region": "us-east-1",
"endpoint": null,
"storage_class": "STANDARD"
},
"sync_options": {
"delete_extraneous": false,
"exclude_patterns": ["*.tmp"],
"dry_run": false,
"verbosity": "normal"
},
"credential_id": 1
}Destination Types:
| Type | Required Config Fields |
|---|---|
local |
mount_point, backup_subdir, usb_uuid?, auto_mount, auto_unmount |
s3 |
bucket, prefix, region, endpoint?, storage_class? |
google_drive |
folder_id, shared_drive_id? |
onedrive |
folder_path, drive_id? |
sftp |
host, port, username, remote_path, key_based_auth |
webdav |
url, remote_path |
Response:
{
"id": 2,
"name": "S3 Backup",
...
}Update an existing job.
PUT /jobs/:id
Authentication: Required
Request Body: Same as create.
Response: Updated job object.
Delete a job and its schedules.
DELETE /jobs/:id
Authentication: Required
Response:
{
"message": "Job deleted"
}Execute a job immediately.
POST /jobs/:id/run
Authentication: Required
Response:
{
"run_id": 10,
"message": "Job started"
}Cancel a running job.
POST /jobs/:id/cancel
Authentication: Required
Response:
{
"message": "Job cancelled"
}Create a copy of an existing job.
POST /jobs/:id/clone
Authentication: Required
Response:
{
"id": 3,
"name": "S3 Backup (Copy)",
...
}Get all schedules for a job.
GET /jobs/:id/schedules
Authentication: Required
Response:
[
{
"id": 1,
"job_id": 1,
"enabled": true,
"schedule_type": "daily",
"hour": 2,
"minute": 0,
"day_of_week": null,
"day_of_month": null,
"cron_expression": null,
"next_run": "2026-01-06T02:00:00Z",
"created_at": "2026-01-01T00:00:00Z"
}
]Add a schedule to a job.
POST /jobs/:id/schedules
Authentication: Required
Request Body (Daily):
{
"schedule_type": "daily",
"hour": 2,
"minute": 0
}Request Body (Weekly):
{
"schedule_type": "weekly",
"hour": 2,
"minute": 0,
"day_of_week": 0
}day_of_week: 0 = Sunday, 6 = Saturday
Request Body (Monthly):
{
"schedule_type": "monthly",
"hour": 2,
"minute": 0,
"day_of_month": 1
}Request Body (Custom Cron):
{
"schedule_type": "custom",
"cron_expression": "0 2 * * 1-5"
}Response: Created schedule object.
Update an existing schedule.
PUT /schedules/:id
Authentication: Required
Request Body: Same as create.
Response: Updated schedule object.
Delete a schedule.
DELETE /schedules/:id
Authentication: Required
Response:
{
"message": "Schedule deleted"
}Get execution history for a job.
GET /jobs/:id/runs
Authentication: Required
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit |
int | Max results (default: 20) |
offset |
int | Skip results (default: 0) |
Response:
[
{
"id": 10,
"job_id": 1,
"job_name": "Daily Backup",
"status": "completed",
"trigger": "scheduled",
"schedule_id": 1,
"started_at": "2026-01-05T02:00:00Z",
"completed_at": "2026-01-05T02:15:00Z",
"files_transferred": 150,
"bytes_transferred": 52428800,
"files_deleted": 0,
"error_message": null
}
]Status Values: pending, running, completed, failed, cancelled
Trigger Values: manual, scheduled
Get details of a specific run.
GET /runs/:id
Authentication: Required
Response: Single run object (same as list item).
Get log entries for a run.
GET /runs/:id/logs
Authentication: Required
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit |
int | Max entries (default: 1000) |
offset |
int | Skip entries (default: 0) |
level |
string | Filter by level (info, warning, error) |
Response:
[
{
"id": 1,
"run_id": 10,
"timestamp": "2026-01-05T02:00:01Z",
"level": "info",
"message": "Starting sync of /home/user/documents"
},
{
"id": 2,
"run_id": 10,
"timestamp": "2026-01-05T02:00:02Z",
"level": "info",
"message": "documents/file.txt"
}
]Delete a specific run and its logs.
DELETE /runs/:id
Authentication: Required
Response:
{
"message": "Run deleted"
}Delete all runs for a job.
DELETE /jobs/:id/runs
Authentication: Required
Response:
{
"message": "Runs deleted",
"count": 15
}Delete all runs across all jobs.
DELETE /runs
Authentication: Required
Response:
{
"message": "All runs deleted",
"count": 100
}Get all saved credentials.
GET /credentials
Authentication: Required
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
provider |
string | Filter by provider type |
Response:
[
{
"id": 1,
"name": "AWS Production",
"provider_type": "s3",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
},
{
"id": 2,
"name": "Backup Server",
"provider_type": "sftp",
"created_at": "2026-01-02T00:00:00Z",
"updated_at": "2026-01-02T00:00:00Z"
}
]Note: Credential secrets are never returned in API responses.
Get a single credential by ID.
GET /credentials/:id
Authentication: Required
Response: Single credential object (without secrets).
Create a new credential.
POST /credentials
Authentication: Required
Request Body (S3):
{
"name": "AWS Production",
"provider_type": "s3",
"data": {
"access_key_id": "AKIAIOSFODNN7EXAMPLE",
"secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
}Request Body (SFTP - Password):
{
"name": "Backup Server",
"provider_type": "sftp",
"data": {
"password": "secretpassword"
}
}Request Body (SFTP - Key):
{
"name": "Backup Server",
"provider_type": "sftp",
"data": {
"private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
"passphrase": "keypassphrase"
}
}Request Body (WebDAV):
{
"name": "Nextcloud",
"provider_type": "webdav",
"data": {
"username": "user",
"password": "password"
}
}Request Body (OAuth - Google Drive/OneDrive):
{
"name": "Google Account",
"provider_type": "google_drive",
"data": {
"access_token": "ya29.a0AfH6...",
"refresh_token": "1//0eXx...",
"expires_at": 1704499200
}
}Response:
{
"id": 3,
"name": "AWS Production",
"provider_type": "s3",
"created_at": "2026-01-05T00:00:00Z",
"updated_at": "2026-01-05T00:00:00Z"
}Update a credential's name or data.
PUT /credentials/:id
Authentication: Required
Request Body:
{
"name": "AWS Production (Updated)",
"data": {
"access_key_id": "AKIAIOSFODNN7NEWKEY",
"secret_access_key": "newSecretKey..."
}
}Note: data is optional - omit to only update name.
Response: Updated credential object.
Delete a credential.
DELETE /credentials/:id
Authentication: Required
Response (success):
{
"message": "Credential deleted"
}Response (in use):
{
"error": "Credential is in use by 2 job(s)",
"jobs": ["Daily Backup", "Weekly Backup"]
}Status: 409 Conflict
Get jobs using a credential.
GET /credentials/:id/usage
Authentication: Required
Response:
{
"credential_id": 1,
"jobs": [
{
"id": 1,
"name": "Daily Backup"
},
{
"id": 2,
"name": "Weekly Backup"
}
]
}Get all available sync providers.
GET /providers
Authentication: Required
Response:
[
{
"type": "local",
"name": "Local / USB",
"description": "Rsync to local or mounted drives",
"requires_credentials": false
},
{
"type": "s3",
"name": "AWS S3",
"description": "S3 and S3-compatible storage",
"requires_credentials": true
},
{
"type": "google_drive",
"name": "Google Drive",
"description": "Google Drive folders and shared drives",
"requires_credentials": true
},
{
"type": "onedrive",
"name": "OneDrive",
"description": "Microsoft OneDrive",
"requires_credentials": true
},
{
"type": "sftp",
"name": "SFTP",
"description": "SSH/SFTP servers",
"requires_credentials": true
},
{
"type": "webdav",
"name": "WebDAV",
"description": "Nextcloud, ownCloud, generic WebDAV",
"requires_credentials": true
}
]Get capabilities for a specific provider.
GET /providers/:type/capabilities
Authentication: Required
Response:
{
"provider_type": "local",
"capabilities": {
"supports_delete": true,
"supports_compression": true,
"supports_checksum": true,
"supports_bandwidth_limit": true,
"supports_exclude_patterns": true,
"supports_incremental": true,
"supports_dry_run": true,
"requires_credentials": false
}
}Test connection to a destination.
POST /providers/test
Authentication: Required
Request Body (with saved credential):
{
"destination_type": "s3",
"destination_config": {
"bucket": "my-bucket",
"prefix": "backups/",
"region": "us-east-1"
},
"credential_id": 1
}Request Body (with inline credential):
{
"destination_type": "sftp",
"destination_config": {
"host": "backup.example.com",
"port": 22,
"username": "backup",
"remote_path": "/backups",
"key_based_auth": false
},
"credential_data": {
"password": "testpassword"
}
}Response (success):
{
"success": true,
"message": "Connection successful",
"details": {
"bucket": "my-bucket",
"region": "us-east-1"
}
}Response (failure):
{
"success": false,
"message": "Access denied: invalid credentials",
"details": null
}Check if the service is running.
GET /system/health
Authentication: None
Response:
{
"status": "ok",
"version": "0.1.0"
}Get available USB drives.
GET /system/drives
Authentication: Required
Response:
[
{
"name": "sdb1",
"label": "BACKUP_DRIVE",
"uuid": "1234-5678",
"fstype": "ntfs",
"size": "500G",
"mountpoint": null
},
{
"name": "sdc1",
"label": "PHOTOS",
"uuid": "ABCD-EFGH",
"fstype": "exfat",
"size": "1T",
"mountpoint": "/mnt/photos"
}
]Get current mount points.
GET /system/mounts
Authentication: Required
Response:
[
{
"device": "/dev/sdc1",
"mountpoint": "/mnt/photos",
"fstype": "exfat",
"options": "rw,nosuid,nodev"
}
]Mount a drive by UUID.
POST /system/mount
Authentication: Required
Request Body:
{
"uuid": "1234-5678",
"mount_point": "/mnt/backup"
}Response:
{
"message": "Drive mounted",
"mount_point": "/mnt/backup"
}Unmount a drive.
POST /system/unmount
Authentication: Required
Request Body:
{
"mount_point": "/mnt/backup"
}Response:
{
"message": "Drive unmounted"
}Browse directory contents.
GET /system/browse
Authentication: Required
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
path |
string | Directory path (default: first allowed path) |
Response:
{
"path": "/home/user",
"parent": "/home",
"entries": [
{
"name": "documents",
"path": "/home/user/documents",
"is_dir": true,
"size": null,
"modified": "2026-01-01T00:00:00Z"
},
{
"name": "file.txt",
"path": "/home/user/file.txt",
"is_dir": false,
"size": 1024,
"modified": "2026-01-01T00:00:00Z"
}
]
}Note: Only paths within ALLOWED_BROWSE_PATHS can be browsed.
Create a new directory.
POST /system/mkdir
Authentication: Required
Request Body:
{
"path": "/mnt/backup/new-folder"
}Response:
{
"message": "Directory created",
"path": "/mnt/backup/new-folder"
}Get paths that can be browsed.
GET /system/allowed-paths
Authentication: Required
Response:
{
"paths": ["/mnt", "/home", "/media"]
}Get application settings.
GET /settings
Authentication: Required
Response:
{
"max_runs_per_job": 10,
"theme": "dark"
}Update application settings.
PUT /settings
Authentication: Required
Request Body:
{
"max_runs_per_job": 20
}Response:
{
"max_runs_per_job": 20,
"theme": "dark"
}Stream real-time logs for a running job.
WebSocket /ws/logs/:run_id?token=<jwt>
Authentication: JWT token as query parameter
Server Messages:
{
"type": "log",
"run_id": 10,
"timestamp": "2026-01-05T02:00:01Z",
"level": "info",
"message": "Starting sync..."
}{
"type": "status",
"run_id": 10,
"status": "completed",
"files_transferred": 150,
"bytes_transferred": 52428800
}Stream status updates for all jobs.
WebSocket /ws/status?token=<jwt>
Authentication: JWT token as query parameter
Server Messages:
{
"type": "job_started",
"job_id": 1,
"run_id": 10,
"job_name": "Daily Backup"
}{
"type": "job_completed",
"job_id": 1,
"run_id": 10,
"job_name": "Daily Backup",
"status": "completed"
}{
"type": "job_progress",
"job_id": 1,
"run_id": 10,
"files_transferred": 50,
"bytes_transferred": 10485760
}All endpoints return consistent error responses:
400 Bad Request:
{
"error": "Invalid request",
"message": "Field 'name' is required"
}401 Unauthorized:
{
"error": "Unauthorized",
"message": "Invalid or expired token"
}403 Forbidden:
{
"error": "Forbidden",
"message": "Access denied"
}404 Not Found:
{
"error": "Not found",
"message": "Job not found"
}409 Conflict:
{
"error": "Conflict",
"message": "Credential is in use"
}429 Too Many Requests:
{
"error": "Rate limited",
"message": "Too many failed attempts. Try again in 60 seconds.",
"retry_after": 60
}500 Internal Server Error:
{
"error": "Internal error",
"message": "An unexpected error occurred"
}Authentication endpoints are rate limited:
- Max attempts: 5 per minute (configurable)
- Lockout: Exponential backoff starting at 60 seconds
- Max lockout: 1 hour
Affected endpoints:
POST /auth/loginPOST /auth/totp/validatePOST /auth/totp/recovery
When rate limited, the response includes a Retry-After header.