API Authentication#
Applications must first authenticate before communicating with INNORIX.
Login and Tokens#
Description#
Log in with your account to obtain an access token (JWT), then include both the Authorization: Bearer and x-workspace-id headers with all subsequent requests. When the token expires, refresh it with POST /api/auth/token/refresh (header: X-Refresh-Token). If you need a long-lived key for command automation, issue one with POST /api/auth/api-keys and use it in the x-api-key header.
APIs Used#
| Purpose | Method | Endpoint |
|---|---|---|
| Login | POST | /api/auth/login |
| Refresh token | POST | /api/auth/token/refresh |
| Issue API key | POST | /api/auth/api-keys |
| Get current user | GET | /api/auth/me |
Request#
POST /api/auth/login
{
"email": "<YOUR_EMAIL>",
"password": "<YOUR_PASSWORD>"
}Response#
{
"status_code": 200,
"message": "success",
"data": {
"user": {
"email": "user@example.com",
"userName": "User Name",
"userId": "usr_abc123",
"accessToken": "<ACCESS_TOKEN>",
"refreshToken": "<REFRESH_TOKEN>"
}
}
}Process#
- Log in with
POST /api/auth/login→ receivedata.user.accessToken - Include the
Authorization: Bearer <ACCESS_TOKEN>andx-workspace-idheaders in subsequent requests - When the token expires, refresh it with
POST /api/auth/token/refresh(header:X-Refresh-Token)
Implementation Examples#
BASE_URL="https://app.innorix.com"
WORKSPACE_ID="<WORKSPACE_ID>"
ACCESS_TOKEN=$(curl -s -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"<YOUR_EMAIL>","password":"<YOUR_PASSWORD>"}' \
| jq -r '.data.user.accessToken')
AUTH=(-H "Authorization: Bearer $ACCESS_TOKEN" -H "x-workspace-id: $WORKSPACE_ID")const BASE_URL = "https://app.innorix.com";
const WORKSPACE_ID = "<WORKSPACE_ID>";
let accessToken = "";
async function login(email, password) {
const res = await fetch(`${BASE_URL}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (!res.ok) throw new Error(`login failed: ${res.status}`);
const json = await res.json();
accessToken = json.data.user.accessToken;
return accessToken;
}
function authHeaders() {
return {
"Authorization": `Bearer ${accessToken}`,
"x-workspace-id": WORKSPACE_ID,
"Content-Type": "application/json",
};
}import time
import requests
BASE_URL = "https://app.innorix.com"
WORKSPACE_ID = "<WORKSPACE_ID>"
session = requests.Session()
def login(email: str, password: str) -> str:
res = session.post(
f"{BASE_URL}/api/auth/login",
json={"email": email, "password": password},
timeout=10,
)
res.raise_for_status()
return res.json()["data"]["user"]["accessToken"]
def set_auth(access_token: str) -> None:
session.headers.update({
"Authorization": f"Bearer {access_token}",
"x-workspace-id": WORKSPACE_ID,
"Content-Type": "application/json",
})import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ExacoolaClient {
static final String BASE_URL = "https://app.innorix.com";
static final String WORKSPACE_ID = "<WORKSPACE_ID>";
final HttpClient http = HttpClient.newHttpClient();
final ObjectMapper mapper = new ObjectMapper();
String accessToken = "";
public String login(String email, String password) throws Exception {
String payload = mapper.writeValueAsString(Map.of("email", email, "password", password));
HttpRequest req = HttpRequest.newBuilder(URI.create(BASE_URL + "/api/auth/login"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
accessToken = mapper.readTree(res.body()).at("/data/user/accessToken").asText();
return accessToken;
}
private HttpRequest.Builder authed(String path) {
return HttpRequest.newBuilder(URI.create(BASE_URL + path))
.header("Authorization", "Bearer " + accessToken)
.header("x-workspace-id", WORKSPACE_ID)
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(15));
}
}using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
class ExacoolaClient
{
const string BaseUrl = "https://app.innorix.com";
const string WorkspaceId = "<WORKSPACE_ID>";
readonly HttpClient http = new() { BaseAddress = new Uri(BaseUrl) };
string accessToken = "";
async Task<string> LoginAsync(string email, string password)
{
var res = await http.PostAsJsonAsync("/api/auth/login", new { email, password });
res.EnsureSuccessStatusCode();
var json = await res.Content.ReadFromJsonAsync<JsonElement>();
accessToken = json.GetProperty("data").GetProperty("user")
.GetProperty("accessToken").GetString()!;
SetAuth(accessToken);
return accessToken;
}
void SetAuth(string token)
{
accessToken = token;
http.DefaultRequestHeaders.Remove("Authorization");
http.DefaultRequestHeaders.Add("Authorization", quot;Bearer {token}");
http.DefaultRequestHeaders.Remove("x-workspace-id");
http.DefaultRequestHeaders.Add("x-workspace-id", WorkspaceId);
}
}System Connectivity#
File transfers take place between connected devices.
Check Devices and Browse Files#
Description#
Retrieve the device list to identify the source and target deviceId, check their connectivity, then browse the source device path to obtain the hash of the item to transfer. (This assumes the agent has already been installed and registered. Once the installed agent connects to the server, the device appears in the device list.)
APIs Used#
| Purpose | Method | Endpoint |
|---|---|---|
| List devices | GET | /api/devices |
| Check connectivity | GET | /api/devices/{deviceId}/connectivity |
| Search files | POST | /api/devices/{deviceId}/files/search |
| List folder contents (non-streaming) | GET | /api/devices/{deviceId}/files |
Request#
POST /api/devices/{deviceId}/files/search
{
"path": "/data/export",
"onlyFolder": false
}Response#
GET /api/devices
{
"status_code": 200,
"message": "success",
"data": {
"devices": [
{ "deviceId": "dev_a1", "name": "seoul-node-01", "os": "linux", "status": "online" },
{ "deviceId": "dev_b2", "name": "hanoi-node-02", "os": "windows", "status": "offline" }
],
"total_rows": 2
}
}Process#
- Use
GET /api/devicesto identify the source and targetdeviceId - Use
GET /api/devices/{deviceId}/connectivityto verify that both devices are online - Use
POST /api/devices/{deviceId}/files/searchto browse the path → obtain the item'shash
Implementation Examples#
# --- List devices ---
curl -s "$BASE_URL/api/devices?page=1&size=20" "${AUTH[@]}"
# --- Check connectivity ---
curl -s "$BASE_URL/api/devices/<DEVICE_ID>/connectivity" "${AUTH[@]}"
# --- Browse a device path ---
curl -s -X POST "$BASE_URL/api/devices/<DEVICE_ID>/files/search" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d '{"path":"C:/data/export","onlyFolder":false}'// --- List devices ---
async function listDevices() {
const url = new URL(`${BASE_URL}/api/devices`);
url.searchParams.set("page", "1");
url.searchParams.set("size", "20");
const res = await fetch(url, { headers: authHeaders() });
if (!res.ok) throw new Error(`device list failed: ${res.status}`);
return (await res.json()).data.devices;
}
// --- Check connectivity ---
async function isOnline(deviceId) {
const res = await fetch(`${BASE_URL}/api/devices/${deviceId}/connectivity`, {
headers: authHeaders(),
});
if (!res.ok) throw new Error(`connectivity failed: ${res.status}`);
return await res.json();
}
// --- Browse a device path ---
async function browse(deviceId, path, onlyFolder = false) {
const res = await fetch(`${BASE_URL}/api/devices/${deviceId}/files/search`, {
method: "POST",
headers: authHeaders(),
body: JSON.stringify({ path, onlyFolder }),
});
if (!res.ok) throw new Error(`browse failed: ${res.status}`);
return await res.json();
}# --- List devices ---
def list_devices() -> list:
res = session.get(
f"{BASE_URL}/api/devices",
params={"page": 1, "size": 20},
timeout=10,
)
res.raise_for_status()
return res.json()["data"]["devices"]
# --- Check connectivity ---
def is_online(device_id: str) -> bool:
res = session.get(
f"{BASE_URL}/api/devices/{device_id}/connectivity",
timeout=10,
)
res.raise_for_status()
return res.json()
# --- Browse a device path ---
def browse(device_id: str, path: str, only_folder: bool = False) -> dict:
res = session.post(
f"{BASE_URL}/api/devices/{device_id}/files/search",
json={"path": path, "onlyFolder": only_folder},
timeout=15,
)
res.raise_for_status()
return res.json()// Methods of the ExacoolaClient class (use with the API authentication code)
// --- List devices ---
public JsonNode listDevices() throws Exception {
HttpRequest req = authed("/api/devices?page=1&size=20").GET().build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
return mapper.readTree(res.body()).at("/data/devices");
}
// --- Check connectivity ---
public JsonNode isOnline(String deviceId) throws Exception {
HttpRequest req = authed("/api/devices/" + deviceId + "/connectivity").GET().build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
return mapper.readTree(res.body());
}
// --- Browse a device path ---
public JsonNode browse(String deviceId, String path, boolean onlyFolder) throws Exception {
String payload = mapper.writeValueAsString(Map.of("path", path, "onlyFolder", onlyFolder));
HttpRequest req = authed("/api/devices/" + deviceId + "/files/search")
.POST(HttpRequest.BodyPublishers.ofString(payload)).build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
return mapper.readTree(res.body());
}// Methods of the ExacoolaClient class (use with the API authentication code)
// --- List devices ---
async Task<JsonElement> ListDevicesAsync()
{
var json = await http.GetFromJsonAsync<JsonElement>("/api/devices?page=1&size=20");
return json.GetProperty("data").GetProperty("devices");
}
// --- Check connectivity ---
async Task<JsonElement> IsOnlineAsync(string deviceId)
{
return await http.GetFromJsonAsync<JsonElement>(quot;/api/devices/{deviceId}/connectivity");
}
// --- Browse a device path ---
async Task<JsonElement> BrowseAsync(string deviceId, string path, bool onlyFolder = false)
{
var res = await http.PostAsJsonAsync(quot;/api/devices/{deviceId}/files/search",
new { path, onlyFolder });
res.EnsureSuccessStatusCode();
return await res.Content.ReadFromJsonAsync<JsonElement>();
}File · Folder Transfer#
Send files from a source device to a target device, control transfers in progress, and check transfer results.
Immediate Transfer and Control#
Description#
Create sourceItem using the hash from the browse results and start a transfer. The API returns a monitorId (data.monitorId in the response). Use this value to pause, resume, cancel, retry failed items, and check results. Poll transfer status with GET /api/transfer-history/{monitorId}?idType=monitor, and retrieve file-level results with GET /api/transfers/{monitorId}/files?idType=monitor.
APIs Used#
| Purpose | Method | Endpoint |
|---|---|---|
| Create transfer | POST | /api/transfers/manual |
| Control transfer | POST | /api/transfers/{monitorId}/pause · resume · cancel |
| Retry failed items | POST | /api/transfers/{monitorId}/retry |
| Get status | GET | /api/transfer-history/{monitorId} |
| Get transfer files | GET | /api/transfers/{monitorId}/files |
Request#
POST /api/transfers/manual
{
"sourceId": "6901ae48ca578216fd739f78",
"targetId": "690037c22d309a7bc494bc53",
"targetPath": "/data/incoming",
"sourceItem": [{ "hash": "a1b2c3d4", "isDir": true }]
}Response#
{
"status_code": 201,
"message": "Created",
"data": { "monitorId": "mon_7788", "transferId": "tr_5566" }
}Process#
- Call
POST /api/transfers/manual→ receivedata.monitorId - Poll status with
GET /api/transfer-history/{monitorId}?idType=monitor - When needed, control the transfer with
POST /api/transfers/{monitorId}/pause·resume·cancel; retry failures with.../retry
Implementation Examples#
# --- Create a transfer ---
curl -s -X POST "$BASE_URL/api/transfers/manual" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d '{
"sourceId": "6901ae48ca578216fd739f78",
"targetId": "690037c22d309a7bc494bc53",
"targetPath": "C:/Users/innorix/Downloads/New folder (3)",
"sourceItem": [
{ "hash": "<FILE_HASH>", "isDir": true }
]
}'
# --- Control a transfer (pause / resume / cancel) ---
curl -s -X POST "$BASE_URL/api/transfers/<MONITOR_ID>/pause" "${AUTH[@]}"
curl -s -X POST "$BASE_URL/api/transfers/<MONITOR_ID>/resume" "${AUTH[@]}"
curl -s -X POST "$BASE_URL/api/transfers/<MONITOR_ID>/cancel" "${AUTH[@]}"
# --- Retry failed files ---
curl -s -X POST "$BASE_URL/api/transfers/<MONITOR_ID>/retry" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d '{
"filesRetry": [
{ "filePath": "C:/data/export/file.txt", "isDir": false }
]
}'
# --- Poll transfer result ---
curl -s "$BASE_URL/api/transfer-history/<MONITOR_ID>?idType=monitor" "${AUTH[@]}"// --- Create a transfer ---
async function createTransfer(sourceId, targetId, targetPath, sourceItems) {
const body = {
sourceId,
targetId,
targetPath,
sourceItem: sourceItems,
};
const res = await fetch(`${BASE_URL}/api/transfers/manual`, {
method: "POST",
headers: authHeaders(),
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`transfer failed: ${res.status}`);
return (await res.json()).data.monitorId;
}
// --- Control a transfer (pause / resume / cancel) ---
async function controlTransfer(monitorId, action) {
const res = await fetch(`${BASE_URL}/api/transfers/${monitorId}/${action}`, {
method: "POST",
headers: authHeaders(),
});
if (!res.ok) throw new Error(`control failed: ${res.status}`);
}
// --- Retry failed files ---
async function retryFailed(monitorId, files) {
const res = await fetch(`${BASE_URL}/api/transfers/${monitorId}/retry`, {
method: "POST",
headers: authHeaders(),
body: JSON.stringify({ filesRetry: files }),
});
if (!res.ok) throw new Error(`retry failed: ${res.status}`);
}
// --- Poll transfer result ---
const TERMINAL = new Set(["completed", "failed", "canceled", "cancelled"]);
async function waitForCompletion(monitorId, intervalMs = 3000) {
while (true) {
const res = await fetch(`${BASE_URL}/api/transfer-history/${monitorId}?idType=monitor`, {
headers: authHeaders(),
});
if (!res.ok) throw new Error(`status failed: ${res.status}`);
const status = (await res.json())?.data?.status;
console.log("transfer status:", status);
if (TERMINAL.has(status)) return status;
await new Promise((r) => setTimeout(r, intervalMs));
}
}# --- Create a transfer ---
def create_transfer(source_id: str, target_id: str,
target_path: str, source_items: list) -> str:
body = {
"sourceId": source_id,
"targetId": target_id,
"targetPath": target_path,
"sourceItem": source_items,
}
res = session.post(
f"{BASE_URL}/api/transfers/manual",
json=body,
timeout=15,
)
res.raise_for_status()
return res.json()["data"]["monitorId"]
# --- Control a transfer (pause / resume / cancel) ---
def control_transfer(monitor_id: str, action: str) -> None:
res = session.post(f"{BASE_URL}/api/transfers/{monitor_id}/{action}", timeout=10)
res.raise_for_status()
# --- Retry failed files ---
def retry_failed(monitor_id: str, files: list) -> None:
res = session.post(
f"{BASE_URL}/api/transfers/{monitor_id}/retry",
json={"filesRetry": files},
timeout=15,
)
res.raise_for_status()
# --- Poll transfer result ---
TERMINAL = {"completed", "failed", "canceled", "cancelled"}
def wait_for_completion(monitor_id: str, interval: float = 3.0) -> str:
while True:
res = session.get(f"{BASE_URL}/api/transfer-history/{monitor_id}?idType=monitor", timeout=10)
res.raise_for_status()
status = res.json().get("data", {}).get("status")
print("transfer status:", status)
if status in TERMINAL:
return status
time.sleep(interval)// Methods of the ExacoolaClient class (use with the API authentication code)
// --- Create a transfer ---
public String createTransfer(String sourceId, String targetId, String targetPath,
List<Map<String, Object>> sourceItems) throws Exception {
Map<String, Object> body = new LinkedHashMap<>();
body.put("sourceId", sourceId);
body.put("targetId", targetId);
body.put("targetPath", targetPath);
body.put("sourceItem", sourceItems);
HttpRequest req = authed("/api/transfers/manual")
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body))).build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
return mapper.readTree(res.body()).at("/data/monitorId").asText();
}
// --- Control a transfer (pause / resume / cancel) ---
public void controlTransfer(String monitorId, String action) throws Exception {
HttpRequest req = authed("/api/transfers/" + monitorId + "/" + action)
.POST(HttpRequest.BodyPublishers.noBody()).build();
http.send(req, HttpResponse.BodyHandlers.ofString());
}
// --- Retry failed files ---
public void retryFailed(String monitorId, List<Map<String, Object>> files) throws Exception {
Map<String, Object> body = new LinkedHashMap<>();
body.put("filesRetry", files);
HttpRequest req = authed("/api/transfers/" + monitorId + "/retry")
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body))).build();
http.send(req, HttpResponse.BodyHandlers.ofString());
}
// --- Poll transfer result ---
public String waitForCompletion(String monitorId, long intervalMs) throws Exception {
Set<String> terminal = Set.of("completed", "failed", "canceled", "cancelled");
while (true) {
HttpRequest req = authed("/api/transfer-history/" + monitorId + "?idType=monitor").GET().build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
String status = mapper.readTree(res.body()).at("/data/status").asText();
System.out.println("transfer status: " + status);
if (terminal.contains(status)) return status;
Thread.sleep(intervalMs);
}
}// Methods of the ExacoolaClient class (use with the API authentication code)
// --- Create a transfer ---
async Task<string> CreateTransferAsync(string sourceId, string targetId, string targetPath,
IEnumerable<object> sourceItems)
{
var body = new
{
sourceId,
targetId,
targetPath,
sourceItem = sourceItems,
};
var res = await http.PostAsJsonAsync("/api/transfers/manual", body);
res.EnsureSuccessStatusCode();
var json = await res.Content.ReadFromJsonAsync<JsonElement>();
return json.GetProperty("data").GetProperty("monitorId").GetString()!;
}
// --- Control a transfer (pause / resume / cancel) ---
async Task ControlTransferAsync(string monitorId, string action)
{
var res = await http.PostAsync(quot;/api/transfers/{monitorId}/{action}", null);
res.EnsureSuccessStatusCode();
}
// --- Retry failed files ---
async Task RetryFailedAsync(string monitorId, IEnumerable<object> files)
{
var res = await http.PostAsJsonAsync(quot;/api/transfers/{monitorId}/retry",
new { filesRetry = files });
res.EnsureSuccessStatusCode();
}
// --- Poll transfer result ---
static readonly HashSet<string> Terminal = new() { "completed", "failed", "canceled", "cancelled" };
async Task<string> WaitForCompletionAsync(string monitorId, int intervalMs = 3000)
{
while (true)
{
var json = await http.GetFromJsonAsync<JsonElement>(quot;/api/transfer-history/{monitorId}?idType=monitor");
var status = json.GetProperty("data").GetProperty("status").GetString();
Console.WriteLine(quot;transfer status: {status}");
if (status != null && Terminal.Contains(status)) return status;
await Task.Delay(intervalMs);
}
}Scheduled Automation#
Configure scheduled transfers that run repeatedly at specified times.
Recurring Automation#
Description#
Create an automation with a schedule and transfer details (details). The API returns an automationId, which you can use to pause, resume, update, or delete the automation. Retrieve progress and status with GET /api/automations/{automationId}/details.
APIs Used#
| Purpose | Method | Endpoint |
|---|---|---|
| Create automation | POST | /api/automations |
| Get automation details | GET | /api/automations/{automationId}/details |
| Pause automation | POST | /api/automations/{automationId}/pause |
| Update automation | PATCH | /api/automations/{automationId} |
| Delete automation | DELETE | /api/automations/{automationId} |
Request#
POST /api/automations
{
"name": "Nightly backup",
"transferType": "scheduled",
"timezone": "Asia/Seoul",
"schedules": [
{ "type": "day", "hour": "02", "minute": "00" }
],
"details": [
{
"sourceItem": ["D:/Projects/data/exported_users.csv"],
"targetPath": "D:/Backup/Daily_Reports",
"senderId": "user123",
"receiverId": "backup_sys_001",
"step": 1
}
]
}Response#
{
"status_code": 200,
"message": "success",
"data": { "automationId": "auto_301", "status": "active" }
}Process#
- Call
POST /api/automations→ receivedata.automationId - Check progress and status with
GET /api/automations/{automationId}/details - Pause/resume with
POST /api/automations/{automationId}/pause; update/delete withPATCH·DELETE
Implementation Examples#
# --- Build a schedule ---
{
"type": "day",
"hour": "02",
"minute": "00",
"timezone": "Asia/Seoul",
"startDate": "2026-01-01T00:00:00.000Z",
"endDate": "2026-12-31T00:00:00.000Z"
}
# --- Create an automation ---
curl -s -X POST "$BASE_URL/api/automations" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d '{
"name": "Nightly backup",
"schedules": [
{
"type": "day",
"hour": "02",
"minute": "00",
"timezone": "Asia/Seoul",
"startDate": "2026-01-01T00:00:00.000Z",
"endDate": "2026-12-31T00:00:00.000Z"
}
],
"details": [
{
"sourceItem": [
"D:/Projects/data/exported_users.csv",
"D:/Projects/data/sales_report.pdf"
],
"targetPath": "D:/Backup/Daily_Reports",
"senderId": "6901ae48ca578216fd739f78",
"receiverId": "690037c22d309a7bc494bc53",
"step": 1,
"fileCount": 2,
"folderCount": 0,
"sizeCount": 0
}
]
}'
# --- Pause / resume an automation ---
curl -s -X POST "$BASE_URL/api/automations/<AUTOMATION_ID>/pause" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d '{"pause": true}'
# --- Update / delete an automation ---
curl -s -X PATCH "$BASE_URL/api/automations/<AUTOMATION_ID>" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d '{ "name": "Nightly backup (updated)" }'
curl -s -X DELETE "$BASE_URL/api/automations/<AUTOMATION_ID>" "${AUTH[@]}"// --- Build a schedule ---
function buildAutomation(hour, minute, timezone, startDate, endDate, type = "day") {
return {
type,
hour,
minute,
timezone,
startDate,
endDate,
};
}
// --- Create an automation ---
async function createAutomation({
name, schedule, senderId, receiverId, targetPath,
sourceItems, fileCount, folderCount = 0, sizeCount = 0,
}) {
const body = {
name,
schedules: [schedule],
details: [
{
sourceItem: sourceItems,
targetPath,
senderId,
receiverId,
step: 1,
fileCount,
folderCount,
sizeCount,
},
],
};
const res = await fetch(`${BASE_URL}/api/automations`, {
method: "POST",
headers: authHeaders(),
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`automation failed: ${res.status}`);
return (await res.json()).data.automationId;
}
// --- Pause / resume an automation ---
async function pauseAutomation(automationId, pause = true) {
const res = await fetch(`${BASE_URL}/api/automations/${automationId}/pause`, {
method: "POST",
headers: authHeaders(),
body: JSON.stringify({ pause }),
});
if (!res.ok) throw new Error(`pause failed: ${res.status}`);
}
// --- Update / delete an automation ---
async function updateAutomation(automationId, body) {
const res = await fetch(`${BASE_URL}/api/automations/${automationId}`, {
method: "PATCH",
headers: authHeaders(),
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`update failed: ${res.status}`);
}
async function deleteAutomation(automationId) {
const res = await fetch(`${BASE_URL}/api/automations/${automationId}`, {
method: "DELETE",
headers: authHeaders(),
});
if (!res.ok) throw new Error(`delete failed: ${res.status}`);
}# --- Build a schedule ---
def build_automation(hour, minute, timezone, start_date, end_date, schedule_type="day"):
return {
"type": schedule_type,
"hour": hour,
"minute": minute,
"timezone": timezone,
"startDate": start_date,
"endDate": end_date,
}
# --- Create an automation ---
def create_automation(name, schedule, sender_id, receiver_id, target_path,
source_items, file_count, folder_count=0, size_count=0):
body = {
"name": name,
"schedules": [schedule],
"details": [
{
"sourceItem": source_items,
"targetPath": target_path,
"senderId": sender_id,
"receiverId": receiver_id,
"step": 1,
"fileCount": file_count,
"folderCount": folder_count,
"sizeCount": size_count,
}
],
}
res = session.post(f"{BASE_URL}/api/automations", json=body, timeout=15)
res.raise_for_status()
return res.json()["data"]["automationId"]
# --- Pause / resume an automation ---
def pause_automation(automation_id, pause=True):
res = session.post(f"{BASE_URL}/api/automations/{automation_id}/pause",
json={"pause": pause}, timeout=10)
res.raise_for_status()
# --- Update / delete an automation ---
def update_automation(automation_id, body):
res = session.patch(f"{BASE_URL}/api/automations/{automation_id}",
json=body, timeout=15)
res.raise_for_status()
def delete_automation(automation_id):
res = session.delete(f"{BASE_URL}/api/automations/{automation_id}", timeout=10)
res.raise_for_status()// Methods of the ExacoolaClient class (use with the API authentication code)
// --- Build a schedule ---
public Map<String, Object> buildAutomation(String hour, String minute, String timezone,
String startDate, String endDate, String type) {
Map<String, Object> s = new LinkedHashMap<>();
s.put("type", type);
s.put("hour", hour);
s.put("minute", minute);
s.put("timezone", timezone);
s.put("startDate", startDate);// "2026-01-01T00:00:00.000Z" (ISO 8601 UTC)
s.put("endDate", endDate);
return s;
}
// --- Create an automation ---
public String createAutomation(String name, Map<String, Object> schedule,
String senderId, String receiverId, String targetPath,
List<String> sourceItems, int fileCount) throws Exception {
Map<String, Object> detail = new LinkedHashMap<>();
detail.put("sourceItem", sourceItems);
detail.put("targetPath", targetPath);
detail.put("senderId", senderId);
detail.put("receiverId", receiverId);
detail.put("step", 1);
detail.put("fileCount", fileCount);
detail.put("folderCount", 0);
detail.put("sizeCount", 0);
Map<String, Object> body = new LinkedHashMap<>();
body.put("name", name);
body.put("schedules", List.of(schedule));
body.put("details", List.of(detail));
HttpRequest req = authed("/api/automations")
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body))).build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
return mapper.readTree(res.body()).at("/data/automationId").asText();
}
// --- Pause / resume an automation ---
public void pauseAutomation(String automationId, boolean pause) throws Exception {
String payload = mapper.writeValueAsString(Map.of("pause", pause));
HttpRequest req = authed("/api/automations/" + automationId + "/pause")
.POST(HttpRequest.BodyPublishers.ofString(payload)).build();
http.send(req, HttpResponse.BodyHandlers.ofString());
}
// --- Update / delete an automation ---
public void updateAutomation(String automationId, Map<String, Object> body) throws Exception {
HttpRequest req = authed("/api/automations/" + automationId)
.method("PATCH", HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body)))
.build();
http.send(req, HttpResponse.BodyHandlers.ofString());
}
public void deleteAutomation(String automationId) throws Exception {
HttpRequest req = authed("/api/automations/" + automationId).DELETE().build();
http.send(req, HttpResponse.BodyHandlers.ofString());
}// Methods of the ExacoolaClient class (use with the API authentication code)
// --- Build a schedule ---
object BuildAutomation(string hour, string minute, string timezone,
string startDate, string endDate, string type = "day")
=> new
{
type,
hour,
minute,
timezone,
startDate,
endDate,
};
// --- Create an automation ---
async Task<string> CreateAutomationAsync(string name, object schedule,
string senderId, string receiverId, string targetPath,
IEnumerable<string> sourceItems, int fileCount, int folderCount = 0, int sizeCount = 0)
{
var body = new
{
name,
schedules = new[] { schedule },
details = new[]
{
new
{
sourceItem = sourceItems,
targetPath,
senderId,
receiverId,
step = 1,
fileCount,
folderCount,
sizeCount,
}
}
};
var res = await http.PostAsJsonAsync("/api/automations", body);
res.EnsureSuccessStatusCode();
var json = await res.Content.ReadFromJsonAsync<JsonElement>();
return json.GetProperty("data").GetProperty("automationId").GetString()!;
}
// --- Pause / resume an automation ---
async Task PauseAutomationAsync(string automationId, bool pause = true)
{
var res = await http.PostAsJsonAsync(quot;/api/automations/{automationId}/pause", new { pause });
res.EnsureSuccessStatusCode();
}
// --- Update / delete an automation ---
async Task UpdateAutomationAsync(string automationId, object body)
{
var res = await http.PatchAsJsonAsync(quot;/api/automations/{automationId}", body);
res.EnsureSuccessStatusCode();
}
async Task DeleteAutomationAsync(string automationId)
{
var res = await http.DeleteAsync(quot;/api/automations/{automationId}");
res.EnsureSuccessStatusCode();
}