INNORIX
Transfer BuilderTransfer FinderDevelopersResourcesCustomers
Start Free
INNORIX

LET FILES
MOVE THEMSELVES

INNORIX provides enterprise file infrastructure for moving and automating files across every system and environment.
Trusted by more than 5,000 enterprise and government agencies.

START HERE

  • Build the Transfer You Need
  • Find the Transfer You Need

POPULAR TRANSFERS

  • Sync Team Folders
  • Send Large Files to Clients
  • Explore Files Across Systems
  • Migrate FTP, SFTP, SCP & rsync
  • Add Transfer to Any App
  • Add Web Upload & Download
  • Build AI & Data Workflows
  • Browse All Transfers→

DEVELOPERS

  • Developer Center
  • Examples
  • API Quickstart
  • Developer Guide
  • API Reference
  • GitHub

RESOURCES

  • Resource Center
  • Product Guide
  • Integrations
  • Deploy & Manage
  • Help Center

CUSTOMERS

  • Government
  • Public Sector
  • Manufacturing
  • Engineering
  • Finance
  • Distribution
  • IT/Telecom
  • Media
  • Healthcare
  • Education

PLANS

  • Pricing

COMPANY

About Us

OTHER INNORIX PRODUCT

Al.bert — Smart Traffic AI

GLOBAL OFFICES

  • New York, USA
  • Seoul, South Korea
  • Ho Chi Minh City, Vietnam
  • View Office Locations→

(C)2026 INNORIX. All rights reserved.

  • Security
  • Status
  • Terms
  • Privacy
  • Cookies
  1. Developers
  2. Developer Guide

Developer Guide

INNORIX developer guide covering API authentication, system connection, file and folder transfer, and schedule automation. Includes login, token, and API key endpoints.

Deploy & Manage
Exabyter
  • Developer Guide
  • Deploy AI Model Files to Multiple Edge Devices
  • Transfer Files from Azure Blob to an On-Premises Server
  • Deploy CI/CD Build Artifacts to Multiple Servers
  • Receive File Transfer Failure and Recovery Alerts in Datadog
  • Convert FTP Batch Jobs to Managed File Flows
  • Transfer Directly from Google Cloud Storage to Amazon S3
  • Create a File Transfer Status Dashboard in Grafana
  • Send Result Files from Kubernetes to Object Storage
  • Automatically Deliver Files That Are Difficult to Manage with Git
  • Scan Received Files with Microsoft Defender and Perform Follow-up Processing
  • Convert rsync Jobs to Managed File Flows

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

json
{
  "email": "<YOUR_EMAIL>",
  "password": "<YOUR_PASSWORD>"
}

Response#

json
{
  "status_code": 200,
  "message": "success",
  "data": {
    "user": {
      "email": "user@example.com",
      "userName": "User Name",
      "userId": "usr_abc123",
      "accessToken": "<ACCESS_TOKEN>",
      "refreshToken": "<REFRESH_TOKEN>"
    }
  }
}

Process#

  1. Log in with POST /api/auth/login → receive data.user.accessToken
  2. Include the Authorization: Bearer <ACCESS_TOKEN> and x-workspace-id headers in subsequent requests
  3. 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

json
{
  "path": "/data/export",
  "onlyFolder": false
}

Response#

GET /api/devices

json
{
  "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#

  1. Use GET /api/devices to identify the source and target deviceId
  2. Use GET /api/devices/{deviceId}/connectivity to verify that both devices are online
  3. Use POST /api/devices/{deviceId}/files/search to browse the path → obtain the item's hash

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

json
{
  "sourceId": "6901ae48ca578216fd739f78",
  "targetId": "690037c22d309a7bc494bc53",
  "targetPath": "/data/incoming",
  "sourceItem": [{ "hash": "a1b2c3d4", "isDir": true }]
}

Response#

json
{
  "status_code": 201,
  "message": "Created",
  "data": { "monitorId": "mon_7788", "transferId": "tr_5566" }
}

Process#

  1. Call POST /api/transfers/manual → receive data.monitorId
  2. Poll status with GET /api/transfer-history/{monitorId}?idType=monitor
  3. 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

json
{
  "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#

json
{
  "status_code": 200,
  "message": "success",
  "data": { "automationId": "auto_301", "status": "active" }
}

Process#

  1. Call POST /api/automations → receive data.automationId
  2. Check progress and status with GET /api/automations/{automationId}/details
  3. Pause/resume with POST /api/automations/{automationId}/pause; update/delete with PATCH · 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(); }
NextDeploy AI Model Files to Multiple Edge Devices

On this page

  • API Authentication
  • Login and Tokens
  • System Connectivity
  • Check Devices and Browse Files
  • File · Folder Transfer
  • Immediate Transfer and Control
  • Scheduled Automation
  • Recurring Automation