/api/wallets/backup

POST

Creates a complete ZIP archive backup of a wallet with timestamped filename.

Description

The `/api/wallets/backup` endpoint creates a complete ZIP archive of a specified wallet, including all coins, transaction history, and configuration files. The backup is saved with a timestamped filename format: `CloudCoin-Wallet-Backup_{wallet_name}_{date}.zip`.

The backup includes:

  • Bank folder: All authenticated coins (13+ RAIDA passes)
  • Fracked folder: Partially authenticated coins (needs healing)
  • Counterfeit folder: Rejected coins
  • Import/Export folders: Incoming and outgoing coins
  • Receipts folder: Complete transaction logs
  • Data folder: transactions.csv and configuration files
💡 Best Practice

Schedule regular backups to prevent data loss. Store backups in multiple secure locations (encrypted drives, offline storage, cloud with encryption).

🔒 Security Notice

Backup files contain your complete wallet including all CloudCoin authenticity numbers (ANs). Store backups in secure, encrypted locations only. Anyone with access to a backup can access your coins.

Parameters

Parameter Type Required Description
destination string Yes Absolute path to the directory where the backup ZIP file will be saved. The directory will be created if it doesn't exist.
📝 Note

The destination parameter must be an absolute path (e.g., C:\Backups on Windows or /home/user/backups on Linux). Relative paths are not supported.

⚠️ Destinations inside Client_Data are rejected

The server canonicalizes destination with _fullpath / realpath and refuses any path that resolves inside the program's own Client_Data directory. This includes ../ traversal and symlinks pointing into Client_Data. Pick a folder outside the program's data directory (e.g. E:\Backups).

🔐 Encryption-aware archival

If /api/system/login has set a password, the backup walks each Bank/Fracked .bin and:

  • Adds already-encrypted files verbatim (whatever key they were under).
  • Re-encrypts plaintext files on the fly through a scratch tempfile under the current login key, so the ZIP never contains plaintext when the user is logged in. Counts surface in the response (files_reencrypted_for_backup, files_added_plaintext).
  • Adds plaintext files verbatim if no key is set (the user has not logged in).

Response

Returns a JSON object with backup details including the full path to the created ZIP file.

Response Properties

command string
Always "wallet-backup".
success bool
Always true on a 200 response.
wallet_path string
Full path to the wallet that was backed up.
wallet_name string
Folder-name of the wallet (e.g. "Default").
zip_path string
Full path to the created ZIP file. Format: {destination}\{wallet_name}_backup.zip.
destination string
Directory path the user supplied.
files_added int
Total .bin files added to the ZIP across Bank and Fracked.
files_skipped int
Files the ZIP writer rejected (rare — typically I/O errors).
files_reencrypted_for_backup int
Files that were plaintext on disk and a key was set, so the backup re-encrypted them on the fly through a scratch tempfile before adding to the ZIP. Greater than zero indicates the user has plaintext remnants — consider running /api/system/encrypt_existing_files.
files_added_plaintext int
Plaintext files added to the ZIP without re-encryption (because no key was set, or the on-the-fly encrypt failed and the backup fell back to verbatim copy).
files_added_multi_warned int
Multi-coin .bin files found inside a wallet folder. Should always be 0; multi-coin is export-only. A non-zero count is logged and indicates a bug elsewhere.
message string
Human-readable success message.

Example Response (logged in, Bank had plaintext remnants)

{
  "command": "wallet-backup",
  "success": true,
  "wallet_path": "E:\\Client_Data\\Wallets\\Default",
  "wallet_name": "Default",
  "zip_path": "E:\\Backups\\Default_backup.zip",
  "destination": "E:\\Backups",
  "files_added": 45,
  "files_skipped": 0,
  "files_reencrypted_for_backup": 45,
  "files_added_plaintext": 0,
  "files_added_multi_warned": 0,
  "message": "Wallet backup created successfully"
}

Error Responses

Missing destination parameter (400 Bad Request)

{
  "status": "error",
  "error": "Missing required parameter: destination",
  "error_code": 1
}

Destination directory does not exist (400 Bad Request)

{
  "error": true,
  "message": "Destination directory does not exist",
  "code": 400
}

Destination inside Client_Data (400 Bad Request)

The destination canonicalized into the program's Client_Data directory (or a subdirectory). Pick a folder outside of it.

{
  "error": true,
  "message": "Backup destination cannot be inside Client_Data — choose a folder outside the program's data directory",
  "code": 400
}

Destination path could not be canonicalized (400 Bad Request)

Returned when the destination cannot be resolved (e.g. broken symlink, malformed path).

{
  "error": true,
  "message": "Destination path could not be resolved",
  "code": 400
}

Cannot create backup directory (500 Internal Server Error)

{
  "status": "error",
  "error": "Cannot create backup directory",
  "error_code": 101
}

Failed to create ZIP archive (500 Internal Server Error)

{
  "status": "error",
  "error": "Failed to create backup zip file",
  "error_code": 101
}

Examples

JavaScript (fetch)

const API_HOST = 'http://localhost:8080';

async function backupWallet(walletPath, destinationPath) {
    try {
        const params = new URLSearchParams({
            wallet_path: walletPath,
            destination: destinationPath
        });

        const response = await fetch(`${API_HOST}/api/wallets/backup?${params}`, {
            method: 'POST'
        });
        const result = await response.json();

        if (result.success) {
            console.log('Backup created successfully!');
            console.log(`Wallet: ${result.wallet_name}`);
            console.log(`ZIP Path: ${result.zip_path}`);
            console.log(`Files Added: ${result.files_added}`);
            console.log(`Message: ${result.message}`);
        } else {
            console.error('Backup failed:', result.message || result.error);
        }

        return result;
    } catch (error) {
        console.error('Error creating backup:', error);
        throw error;
    }
}

// Usage - Windows
backupWallet('E:\\Client_Data\\Wallets\\Default', 'E:\\Backups');

// Usage - Linux/macOS
// backupWallet('E:/Client_Data/Wallets/Default', '/home/user/backups');

cURL

# Windows example
curl -X POST "http://localhost:8080/api/wallets/backup?wallet_path=E:/Client_Data/Wallets/Default&destination=E:/Backups"

# Linux/macOS example
# curl -X POST "http://localhost:8080/api/wallets/backup?wallet_path=E:/Client_Data/Wallets/Default&destination=/home/user/backups"

Go

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)

const ApiHost = "http://localhost:8080"

type BackupResponse struct {
    Status      string `json:"status"`
    Operation   string `json:"operation"`
    Wallet      string `json:"wallet"`
    Destination string `json:"destination"`
    Filename    string `json:"filename"`
    FullPath    string `json:"full_path"`
    Message     string `json:"message"`
}

type ErrorResponse struct {
    Status    string `json:"status"`
    Error     string `json:"error"`
    ErrorCode int    `json:"error_code"`
}

func backupWallet(walletPath, destination string) (*BackupResponse, error) {
    // Build URL with query parameters
    params := url.Values{}
    params.Add("wallet_path", walletPath)
    params.Add("destination", destination)

    url := fmt.Sprintf("%s/api/wallets/backup?%s", ApiHost, params.Encode())

    req, err := http.NewRequest("POST", url, nil)
    if err != nil {
        return nil, fmt.Errorf("failed to create request: %w", err)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("request failed: %w", err)
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("failed to read response: %w", err)
    }

    // Check for error response
    if resp.StatusCode != http.StatusOK {
        var errResp ErrorResponse
        if err := json.Unmarshal(body, &errResp); err != nil {
            return nil, fmt.Errorf("failed to parse error response: %w", err)
        }
        return nil, fmt.Errorf("backup failed: %s (code %d)", errResp.Error, errResp.ErrorCode)
    }

    var result BackupResponse
    if err := json.Unmarshal(body, &result); err != nil {
        return nil, fmt.Errorf("failed to parse response: %w", err)
    }

    return &result, nil
}

func main() {
    // Windows example
    walletPath := "E:\\Client_Data\\Wallets\\Default"
    destination := "E:\\Backups"

    // Linux/macOS example
    // walletPath := "E:/Client_Data/Wallets/Default"
    // destination := "/home/user/backups"

    result, err := backupWallet(walletPath, destination)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Printf("Backup created successfully!\n")
    fmt.Printf("Wallet: %s\n", result.Wallet)
    fmt.Printf("Filename: %s\n", result.Filename)
    fmt.Printf("Full Path: %s\n", result.FullPath)
    fmt.Printf("Message: %s\n", result.Message)
}

Backup Process Details

The backup process follows these steps:

  1. Validate Parameters: Checks that destination path is provided and not empty
  2. Verify Active Wallet: Ensures a wallet is currently active
  3. Generate Filename: Creates timestamped filename with format: CloudCoin-Wallet-Backup_{wallet_name}_{YYYY-MM-DD}.zip
  4. Create Directory: Creates destination directory if it doesn't exist (with appropriate permissions)
  5. Archive Wallet: Uses platform-specific ZIP compression (PowerShell on Windows, zip command on Linux/Unix)
  6. Verify Success: Checks that ZIP file was created successfully
  7. Return Details: Returns JSON response with full path and metadata
💡 Filename Format

The backup filename includes both the wallet name and date for easy identification. Example: CloudCoin-Wallet-Backup_Default_2025-01-31.zip

This makes it simple to track multiple backups across different wallets and dates.

Restoring from Backup

To restore a wallet from a backup ZIP file:

  1. Extract ZIP: Unzip the backup file to a new directory
  2. Add Location: Use /api/wallets/locations to register the extracted wallet
  3. Verify Balance: Use /api/wallets/balance with the restored wallet_path to confirm all coins are present
📝 Verification After Restore

After restoring a wallet, verify the coin count and balance match the pre-backup state. You may want to run /api/wallets/show-coins to inspect individual coins.

Related Endpoints

/api/wallets/locations

Register a new wallet location or restore a wallet from backup by adding its extracted directory.

/api/wallets/show-coins

View all coins in a wallet before creating a backup to verify contents. Requires wallet_path.

/api/wallets/balance

Check wallet balance before and after backup/restore to verify coin integrity. Requires wallet_path.