Find and Transfer Files Across Multiple Devices from One Place

Getting Started

Basic Concept

View files across multiple devices in one place

Unified File Explorer connects multiple devices so you can browse the files and folders on each device from a single interface.

Users can select a connected device, browse its folders, locate the files they need, and work with files across multiple devices from one workspace.

For example, you can view documents on a work PC, data on a server, and result files in storage from the same browsing environment and select the files you need.

Exploration Flow

Move from device selection to file review and transfer

Unified File Explorer provides a workflow for finding files on connected devices and transferring selected files to the next work environment.

① Select a device

② Browse folders

③ Review files

④ Select the required files

⑤ Select a transfer target

⑥ Transfer files

⑦ Review results

This flow takes you from locating files to transferring them to the required work location and reviewing the results.

Workflow Changes

Connect file browsing and transfer in a single workflow

When work depends on files across multiple devices, users must identify where the required files are stored, locate them on the relevant device, and prepare them for the next work environment.

With Unified File Explorer, users can find the files they need on connected devices and transfer selected files directly to a specified device or workspace.

CategoryFile Management by DeviceUnified File Explorer
Start WorkIdentify the device containing the required filesSelect directly from the connected device list
Browse FilesReview files and folders in each device environmentBrowse devices and files from one screen
Prepare FilesPrepare files for the next work location after reviewing themTransfer selected files directly to the target device
Continue WorkContinue to the next task after preparing filesUse transferred files immediately after completion

By combining file browsing and transfer into one flow, files across multiple work environments can be used directly where they are needed.

IT Engineers

Configure and manage a file browsing environment across multiple devices

Connect Devices

Connect devices for browsing and expand the environment as needed

To configure Unified File Explorer, first connect the PCs, servers, storage systems, and other devices that contain the files you need to access.

After configuring the connection information for each device, you can browse that system's files and folders from the unified explorer.

As the work environment expands, you can add new servers or storage systems in the same way. After configuring their connection information, the new devices can be included in the existing browsing environment.

Device TypeFiles Used
Work PCPersonal and team work files
Windows ServerBusiness documents and operational files
Linux ServerData and processing files
StorageShared files and result files

Adding devices extends the browsing scope to additional systems while preserving the existing file exploration environment.

Access Scope

Define which files and folders users can access on each device

After connecting devices, configure which devices and file paths users can access based on their roles and responsibilities.

You can assign devices to individual users or user groups and define the folder scope available on each device.

For example, the operations team can be given access to designated folders on operational servers, while the data team can access work paths on analytics servers and data storage.

User GroupDevices to BrowseFile Scope
Operations TeamOperations ServerOperational file paths
Data TeamAnalytics ServerData folders
Business TeamShared StorageWork file folders

Defining access scopes by user lets you operate Unified File Explorer around the devices and files each team needs for its work.

Browse Files

Find the files you need across connected devices

After devices and access scopes are configured, users can select a device in the unified explorer and browse its folders and files.

Selecting a system from the device list displays its folder structure and file list, allowing users to locate files by name and path.

The file browser displays the following information:

ItemDescription
DeviceDevice containing the file
PathCurrent file path
File NameFile name
SizeFile size
ModifiedLast modified time

After locating a file, select it to continue directly to the next transfer operation.

File Transfer

Transfer selected files to the required device or workspace

After selecting the required files in the explorer, specify the destination device and target path.

The selected files are transferred from their current location to the specified target device or workspace, where they can be reviewed and used in the next task.

The transfer flow is structured as follows:

text
Device A
   │
   │ Browse files
   ▼
Select files
   │
   │ Select target
   ▼
Device B
   │
   ▼
Workspace

Finding files and selecting a destination from the same explorer connects file browsing and transfer in a single workflow.

Verify Results

Review transfer status and file processing results

When a file transfer runs, you can review its progress and processing results in Runs.

Each run shows the Source and Target for the transferred files, file count, transfer volume, progress, execution time, and current status.

ItemDetails
SourceDevice and path where the files were selected
TargetDevice and workspace receiving the files
FilesNumber of processed files
SizeTotal transfer volume
ProgressCurrent transfer progress
StatusCurrent run status
TimeExecution and completion times

Operators can use run results to review file transfer flows and processing status between devices and manage how files are delivered to each work environment.

Developers

Retrieve file lists and search results from remote devices through the API and run transfers between devices

Integration Setup

Prepare shared API calls and status values

import os
import requests

BASE_URL = os.getenv("INNORIX_BASE_URL", "https://app.innorix.com").rstrip("/")
TOKEN = os.environ["INNORIX_ACCESS_TOKEN"]
WORKSPACE_ID = os.getenv("INNORIX_WORKSPACE_ID")   # optional; falls back to the current workspace

STATUS_COMPLETE = 2
TERMINAL = {2, 4, 5, 9, 99}          # complete / error / cancelled / partial / failed
NOT_SUCCEEDED = {4, 5, 9, 99}


def api(method, path, body=None, params=None):
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {TOKEN}",
    }

    if WORKSPACE_ID:
        headers["x-workspace-id"] = WORKSPACE_ID

    response = requests.request(
        method, BASE_URL + path,
        headers=headers, json=body, params=params, timeout=30,
    )

    payload = response.json() if response.content else {}

    if not response.ok:
        raise RuntimeError(payload.get("message") or f"HTTP {response.status_code}")

    return payload.get("data")


def is_terminal(detail):
    return detail.get("isTerminal", detail.get("status") in TERMINAL)

Use the following values to determine transfer status. There are five terminal states, and Complete (2) is the successful state.

Status ValueMeaningTerminal
2CompleteYes
4ErrorYes
5CancelledYes
9Partial CompleteYes
99FailedYes
1 · 6 · 12 · 13Starting · Transferring · Synchronizing · ReceivingNo

List Folder Contents

Retrieve a device file list and display it in the UI

Specify a device identifier and path to retrieve the files and subfolders in that folder. Because the value passed to --device is used as a path parameter, it must be a device ID.

result = api("GET", f"/api/devices/{device_id}/files", params={
    "path": "/data/reports",
    "page": 1,
    "size": 50,
    "sort": "name:asc",
    "type": "all",
})

for item in result["items"]:
    kind = "DIR " if item["isDir"] else "FILE"
    print(kind, item["name"], item["size"], item.get("modifiedAt"))
Response FieldDescription
itemsList of files and folders
total · lastPageTotal item count and final page
truncatedWhether only part of the result was returned because the item limit was exceeded
isDirWhether the item is a folder

Use the device list endpoint to find the device ID.

result = api("GET", "/api/devices", params={"page": 1, "size": 200})

for device in result["devices"]:
    print(device["deviceId"], device["name"], device.get("os"),
          device.get("ipAddress"))

The device list is returned in the data.devices array.

Search recursively through subfolders to find files

Folder listing returns only the current folder. To search through subfolders, start a search and retrieve subsequent results using the cursor.

RESTART_CODES = {"INVALID_CURSOR", "CURSOR_OUT_OF_SEQUENCE", "SEARCH_EXPIRED"}


def start_search(device_id, path, page_size=500):
    return api("POST", f"/api/devices/{device_id}/files/search",
               {"path": path, "pageSize": page_size})


def iter_search(device_id, path, max_pages=200):
    page = start_search(device_id, path)
    search_id = page.get("searchId")

    for _ in range(max_pages):
        for item in page.get("items") or []:
            yield search_id, item

        if not page.get("hasMore"):
            return

        try:
            page = api("GET", f"/api/devices/{device_id}/files/search",
                       params={"cursor": page["nextCursor"]})
        except RuntimeError:
            # restart the scan when the cursor expires or falls out of sequence
            page = start_search(device_id, path)
            search_id = page.get("searchId")

Search requests accept only the base path and page size. Name and extension filters are applied to the returned results, so using a narrower base path reduces the search scope.

Response FieldDescription
searchIdSearch identifier passed when cancelling a search
items[].typefile or directory
hasMore · nextCursorWhether another page exists and the cursor used to retrieve it

Stop a Search

Stop an active search

A search causes the device to scan its disk. Cancel the search when the user leaves the screen to prevent unnecessary load from accumulating on the device.

def cancel_search(device_id, search_id):
    api("POST", f"/api/devices/{device_id}/files/search/cancel",
        {"uuid": search_id})       # pass the searchId returned when the search started


search_id = None

try:
    for search_id, item in iter_search(device_id, "/data"):
        if item["type"] == "file" and item["name"].endswith(".csv"):
            print(item["path"], item["size"])
finally:
    if search_id:
        cancel_search(device_id, search_id)

Run the same cleanup logic both when starting a new search and when closing the screen.

File Transfer

Send selected files to another device

For an immediate transfer, a device can be specified by name, IP address, or identifier, and paths are passed as plain-text strings. This differs from the browsing API, which accepts only device IDs.

When sending a list of files, use sourceItem with isDir: false instead of sourcePaths. sourcePaths treats every path as a folder, so passing files causes the server to scan each file as a folder, which can slow the request or cause it to time out.

def send_files(source, target, target_path, paths, action="numbering"):
    transfer = api("POST", "/api/transfers/manual", {
        "sourceDevice": source,
        "targetDevice": target,
        "targetPath": target_path,
        "sourceItem": [{"path": p, "isDir": False} for p in paths],
        "sendAllFolder": False,
        "transferOptions": {"target-action": action},
    })

    return transfer["monitorId"]


monitor_id = send_files("device-a", "device-b", "/data/collected",
                        ["/data/reports/2026-08.csv"])

When sending folders, use sourcePaths with sendAllFolder: True.

api("POST", "/api/transfers/manual", {
    "sourceDevice": "device-a",
    "targetDevice": "device-b",
    "targetPath": "/data/collected",
    "sourcePaths": ["/data/reports"],
    "sendAllFolder": True,
    "transferOptions": {"target-action": "numbering"},
})

If the file size is already known, pass fileSize to skip the server's per-item lookup.

"sourceItem": [
  { "path": "/data/a.csv", "isDir": false, "fileSize": 1200 }
]

Verify Results

Review transfer status and per-file processing results

import time


def wait(monitor_id, timeout=1800, interval=3):
    deadline = time.time() + timeout

    while time.time() < deadline:
        detail = api("GET", f"/api/transfers/{monitor_id}")

        if is_terminal(detail):
            return detail

        time.sleep(interval)

    raise TimeoutError(monitor_id)


detail = wait(monitor_id)
succeeded = detail["status"] == STATUS_COMPLETE

Full failures and partial failures should be displayed differently in the UI. Check each file's status through the file listing endpoint and retry only the failed files.

def failed_files(monitor_id):
    result = api("GET", f"/api/transfers/{monitor_id}/files", params={
        "state": "any", "size": 500,
    }) or {}

    return [r for r in (result.get("children") or [])
            if r.get("status") in NOT_SUCCEEDED]


def retry_failed(monitor_id):
    rows = failed_files(monitor_id)

    if not rows:
        return 0

    api("POST", f"/api/transfers/{monitor_id}/retry", {
        "filesRetry": [
            {"filePath": r["sourceFilePath"], "isDir": bool(r.get("isFolder"))}
            for r in rows
        ]
    })

    return len(rows)

Retries can be requested only after the transfer reaches a terminal state. A retry is rejected while the transfer is still in progress, so check the status first.

ItemDetails
statusTransfer status value
statusLabelStatus string displayed in the UI
percentProgress percentage
fileCount · totalSizeNumber of processed files and total size
children[].errorCodePer-file failure reason