Create a One-to-One Transfer

Send files between two devices and track progress to completion.

The core flow. This example creates an immediate transfer from a source device to a target device, then polls the transfer status until it finishes. Status is an integer code and percent runs from 0 to 100.

Steps

  1. Log in and confirm both devices are online.
  2. Create the transfer with POST /api/transfer/manualTransfer.
  3. Read the returned monitorId.
  4. Poll GET /api/transfer/{monitorId}/detail-unified until a terminal status.

Configuration

Set your credentials and IDs via environment variables or a .env file (INNORIX_BASE_URL, INNORIX_WORKSPACE_ID, INNORIX_EMAIL, INNORIX_PASSWORD, and the INNORIX_SOURCE_ID / INNORIX_TARGET_ID / path variables the example uses).

API

MethodEndpointDescription
GET/api/device/connectivity/{deviceId}Check whether a device is online
POST/api/transfer/manualTransferCreate a one-to-one transfer
GET/api/transfer/{monitorId}/detail-unifiedGet transfer status and progress
python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
""" 04 - One-to-one transfer + progress monitoring ============================================= The core flow: create an immediate transfer between two registered devices, then poll its status until it reaches a terminal state. Status is an integer code (see TRANSFER_STATUS below); percent is 0-100. This file is self-contained: it embeds a minimal client so it can be read and run on its own. Endpoints: POST /api/transfer/manualTransfer GET /api/transfer/{monitorId}/detail-unified Run: python 04_transfer_manual.py """ from __future__ import annotations import os import time import json import logging import requests try: from dotenv import load_dotenv load_dotenv() except ImportError: pass # --------------------------------------------------------------------------- # # Configuration # --------------------------------------------------------------------------- # BASE_URL = os.getenv("INNORIX_BASE_URL", "https://app.innorix.com") WORKSPACE_ID = os.getenv("INNORIX_WORKSPACE_ID", "<WORKSPACE_ID>") EMAIL = os.getenv("INNORIX_EMAIL", "<YOUR_EMAIL>") PASSWORD = os.getenv("INNORIX_PASSWORD", "<YOUR_PASSWORD>") SOURCE_ID = os.getenv("INNORIX_SOURCE_ID", "<SOURCE_DEVICE_ID>") TARGET_ID = os.getenv("INNORIX_TARGET_ID", "<TARGET_DEVICE_ID>") TARGET_PATH = os.getenv("INNORIX_TARGET_PATH", "C:/Users/innorix/Downloads") SOURCE_PATHS = [p.strip() for p in os.getenv( "INNORIX_SOURCE_PATHS", "C:/Users/innorix/Downloads/image").split(",") if p.strip()] logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S") log = logging.getLogger("innorix") # Transfer status codes (must match the server-side enum). TRANSFER_STATUS = { -1: "transferQueue", 0: "WaitingTransfer", 1: "StartTransfer", 2: "transferComplete", 3: "transferPause", 4: "transferError", 5: "transferCancel", 6: "transferring", 7: "transferSkip", 8: "transferRetry", 9: "transferPartialComplete", 10: "transferRetryChecking", 11: "virusScanning", 12: "syncing", 13: "transferInComming", 14: "transferActivity", 99: "transferFail", } TERMINAL_STATES = {2, 4, 5, 9, 99} # complete / error / cancel / partial / fail def status_name(code): if code is None: return "unknown" return TRANSFER_STATUS.get(code, f"unknown({code})") # --------------------------------------------------------------------------- # # Minimal client # --------------------------------------------------------------------------- # class ApiError(RuntimeError): """Raised when the API returns a non-2xx response.""" class InnorixClient: def __init__(self, base_url: str, workspace_id: str, timeout: float = 15.0): self.base_url = base_url.rstrip("/") self.workspace_id = workspace_id self.timeout = timeout self.session = requests.Session() def _request(self, method: str, path: str, **kwargs): kwargs.setdefault("timeout", self.timeout) res = self.session.request(method, f"{self.base_url}{path}", **kwargs) try: res.raise_for_status() except requests.HTTPError as exc: raise ApiError(f"{method} {path} -> {res.status_code}: {res.text[:500]}") from exc if not res.content: return None try: return res.json() except ValueError: return res.text def login(self, email: str, password: str) -> str: data = self._request("POST", "/api/auth/login", json={"email": email, "password": password}, timeout=10) token = data["data"]["user"]["accessToken"] self.session.headers.update({ "Authorization": f"Bearer {token}", "x-workspace-id": self.workspace_id, "Content-Type": "application/json", }) log.info("logged in") return token def is_online(self, device_id: str) -> bool: result = self._request("GET", f"/api/device/connectivity/{device_id}", timeout=10) if isinstance(result, dict): return bool(result.get("data", result)) return bool(result) def create_transfer(self, source_id, target_id, target_path, source_file_paths) -> str: body = { "sourceId": source_id, "targetId": target_id, "targetPath": target_path, "sourceItem": [{"filePath": p} for p in source_file_paths], } data = self._request("POST", "/api/transfer/manualTransfer", json=body) monitor_id = data["data"]["monitorId"] log.info("transfer created monitorId=%s", monitor_id) return monitor_id def get_detail(self, monitor_id: str) -> dict: """GET /api/transfer/{monitorId}/detail-unified (status/percent at top level).""" data = self._request("GET", f"/api/transfer/{monitor_id}/detail-unified", params={"workSpaceId": self.workspace_id}, timeout=10) if isinstance(data, dict) and "status" not in data and isinstance(data.get("data"), dict): return data["data"] return data def wait_for_completion(self, monitor_id: str, interval: float = 3.0) -> int: while True: detail = self.get_detail(monitor_id) raw = detail.get("status") status = int(raw) if raw is not None else None percent = detail.get("percent") log.info("status=%s(%s) percent=%s", status, status_name(status), f"{percent}%" if percent is not None else "-") if status in TERMINAL_STATES: return status time.sleep(interval) # --------------------------------------------------------------------------- # # Run # --------------------------------------------------------------------------- # def main() -> None: client = InnorixClient(BASE_URL, WORKSPACE_ID) client.login(EMAIL, PASSWORD) # Guard: both devices should be online, otherwise the transfer may stall. for role, device_id in (("source", SOURCE_ID), ("target", TARGET_ID)): if not client.is_online(device_id): raise SystemExit(f"{role} device is offline: {device_id}") monitor_id = client.create_transfer( source_id=SOURCE_ID, target_id=TARGET_ID, target_path=TARGET_PATH, source_file_paths=SOURCE_PATHS, ) print("monitorId:", monitor_id) final = client.wait_for_completion(monitor_id, interval=3.0) print(f"final status: {final} ({status_name(final)})") if __name__ == "__main__": main()