INNORIX

INNORIX PLATFORM

INNORIX Platform시스템 전체의 파일 전송을 하나의 플랫폼에서 운영합니다.
  • 파일 전송 자동화
  • 서버 간 파일 전송
  • 객체 스토리지 간 전송
  • 파일 배포 및 수집

WEB

Exabyter웹에서 대용량 파일을 빠르고 안정적으로 업로드하고 다운로드합니다.
  • 웹 대용량 파일 업로드 & 다운로드
  • 개발자 가이드

DEVELOPERS

앱 임베디드 파일 전송기존 애플리케이션에 파일 전송 기능을 통합합니다.
  • 앱 임베디드 파일 전송

도구

전송 빌더원하는 전송을 직접 구성합니다.전송 파인더업무에 맞는 전송을 찾습니다.

성능과 규모

  • 고속 파일 전송
  • 대용량 파일 전송
  • 대량 파일 전송

현대화

  • FTP, SFTP 마이그레이션

새로운 전송 환경

  • AI Data Delivery
  • Dynamic Endpoint Transfer

글로벌 대규모 전송

Hyperlane수백 TB에서 PB까지 국가와 대륙을 넘어 전송합니다.
개발자리소스고객사
무료 시작하기
INNORIX

LET FILES
MOVE THEMSELVES

INNORIX는 모든 시스템과 환경에서 파일 이동과 자동화를 제공하는 엔터프라이즈 파일 인프라 기업입니다.
5,000개 이상의 기업 및 공공기관에서 사용하고 있습니다.

파일 전송

INNORIX PLATFORM

INNORIX Platform
  • 파일 전송 자동화
  • 서버 간 파일 전송
  • 객체 스토리지 간 전송
  • 파일 배포 및 수집

PERFORMANCE & SCALE

  • 고속 파일 전송
  • 대용량 파일 전송
  • 대량 파일 전송

MODERNIZATION

  • FTP, SFTP 마이그레이션

WEB

Exabyter
  • 웹 대용량 파일 업로드 & 다운로드
  • 개발자 가이드

DEVELOPERS

  • 앱 임베디드 파일 전송

NEW WORKLOADS

  • AI Data Delivery
  • Dynamic Endpoint Transfer

HYPERLANE

  • 서비스 소개
  • 서비스 문의

개발자

  • 개발자 센터
  • 구현 예제
  • API 빠른 시작
  • 개발자 가이드
  • API 레퍼런스
  • GitHub

도구

  • 전송 빌더
  • 전송 파인더

리소스

  • 리소스 센터
  • 제품 가이드
  • 외부 서비스 연동
  • 배포 및 관리
  • 도움말 센터

고객사

  • 정부
  • 공공부문
  • 제조
  • 엔지니어링
  • 금융
  • 유통
  • IT/통신
  • 미디어
  • 의료
  • 교육

플랜

  • 가격 및 플랜

회사

INNORIX 소개

비전 AI 제품

Al.bert — 스마트 교통 AI

글로벌 오피스

  • 미국 뉴욕
  • 대한민국 서울
  • 베트남 호찌민
  • 오피스 위치 보기→

(C)2026 INNORIX. All rights reserved.

  • 보안
  • 상태
  • 이용약관
  • 개인정보 처리방침
  • 쿠키 정책
  1. 개발자
  2. 개발자 가이드

개발자 가이드

INNORIX 개발자 가이드로 API 인증, 시스템 연결, 파일 및 폴더 전송, 예약 자동화에 대해 다룹니다. 로그인, 토큰 및 API 키 엔드포인트에 대한 정보도 포함되어 있습니다.

배포 및 관리
  • 개발자 가이드
  • AI 모델 파일을 여러 엣지 장비에 배포하기
  • Azure Blob에서 사내 서버로 파일 전달하기
  • CI/CD 빌드 결과물을 여러 서버에 배포하기
  • Datadog으로 파일 전송 실패와 복구 알림 받기
  • FTP 배치 작업을 관리형 파일 흐름으로 전환하기
  • Google Cloud Storage에서 Amazon S3로 직접 전송하기
  • Grafana에서 파일 전송 상태 대시보드 만들기
  • Kubernetes에서 오브젝트 스토리지로 결과 파일 보내기
  • Git으로 관리하기 어려운 파일을 자동 전달하기
  • 수신 파일을 Microsoft Defender로 검사하고 후속 처리하기
  • rsync 작업을 관리형 파일 흐름으로 전환하기

본 문서는 INNORIX Public API v2.0.0 기준입니다. 모든 응답은 statusCode, message, data, isCached 형식이며, status_code(snake_case)는 하위호환을 위해 남아 있지만 statusCode 사용을 권장합니다.

API 인증#

애플리케이션이 INNORIX와 통신하려면 먼저 인증해야 합니다.

로그인과 토큰#

설명#

계정으로 로그인해 액세스 토큰(JWT) 을 발급받고, 이후 모든 요청에 Authorization: Bearer와 x-workspace-id 헤더를 함께 보냅니다. 토큰이 만료되면 POST /api/auth/token/refresh(헤더 X-Refresh-Token, 본문 없음)로 갱신합니다. 명령 자동화용 장기 키가 필요하면 POST /api/auth/api-keys(본문 없음)로 발급해 x-api-key 헤더로 사용합니다. 사용자당 활성 API 키는 1개이며, 재호출 시 동일한 키가 반환됩니다.

사용 API#

목적 Method Endpoint
로그인 POST /api/auth/login
토큰 갱신 POST /api/auth/token/refresh
API 키 발급 POST /api/auth/api-keys
현재 사용자 조회 GET /api/auth/me

Request#

POST /api/auth/login

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

Response#

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

처리 순서#

  1. POST /api/auth/login으로 로그인 → data.user.accessToken 수신
  2. 이후 요청에 Authorization: Bearer <ACCESS_TOKEN>와 x-workspace-id 헤더 부착
  3. 토큰 만료 시 POST /api/auth/token/refresh(헤더 X-Refresh-Token)로 갱신 → data.accessToken, data.refreshToken, data.expiresIn 수신

구현 예제#

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 InnorixClient {
    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 InnorixClient
{
    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); } }

시스템 연결#

파일 전송은 연결된 장비(device) 사이에서 일어납니다.

디바이스 확인과 파일 탐색#

설명#

장비 목록을 조회하거나, 이름, IP, MAC을 GET /api/devices/resolve로 정확한 deviceId로 변환합니다. 즉시 전송(POST /api/transfers/manual)은 소스, 대상에 deviceId뿐 아니라 이름, IP도 그대로 받고 평문 경로를 전송하므로, 대부분의 경우 별도 탐색 없이 바로 전송할 수 있습니다. 필요할 때만 POST /api/devices/{deviceId}/files/search로 경로를 배치 탐색합니다. 이 탐색은 커서 기반 페이지네이션으로, 응답의 nextCursor를 GET /api/devices/{deviceId}/files/search?cursor=에 넘겨 hasMore가 false가 될 때까지 반복합니다. (에이전트 설치, 등록은 완료된 상태를 전제로 하며, 설치된 에이전트가 서버에 연결되면 장비 목록에 나타납니다.)

사용 API#

목적 Method Endpoint
디바이스 목록 GET /api/devices
디바이스 확인(이름, IP, MAC → ID) GET /api/devices/resolve
연결 상태 조회 GET /api/devices/{deviceId}/connectivity
파일 검색 시작 POST /api/devices/{deviceId}/files/search
파일 검색 다음 배치 GET /api/devices/{deviceId}/files/search?cursor=

Request#

POST /api/devices/{deviceId}/files/search

json
{
  "path": "C:/data/export",
  "pageSize": 500
}

Response#

GET /api/devices/resolve?name=seoul-node-01

json
{
  "statusCode": 200,
  "message": "success",
  "data": {
    "matchCount": 1,
    "devices": [
      { "deviceId": "6901ae48ca578216fd739f78", "name": "seoul-node-01", "os": "linux", "status": 1 }
    ]
  }
}

GET /api/devices/{deviceId}/connectivity

json
{
  "statusCode": 200,
  "message": "success",
  "data": { "deviceId": "6901ae48ca578216fd739f78", "state": 1, "isConnected": true }
}

POST /api/devices/{deviceId}/files/search

json
{
  "statusCode": 200,
  "message": "OK",
  "data": {
    "searchId": "srch_9f2a3c",
    "items": [
      { "name": "report.pdf", "path": "C:/data/export/report.pdf", "type": "file", "size": 20480 }
    ],
    "count": 1,
    "hasMore": false,
    "nextCursor": null
  }
}

처리 순서#

  1. GET /api/devices 또는 GET /api/devices/resolve로 소스, 대상 deviceId 확인 (즉시 전송에는 이름, IP도 그대로 사용 가능)
  2. GET /api/devices/{deviceId}/connectivity로 두 장비 isConnected 여부 확인
  3. (선택) POST /api/devices/{deviceId}/files/search({path, pageSize})로 첫 배치 조회 → nextCursor, hasMore 확인, GET ...?cursor=로 반복. 자동화의 sourceItem은 {deviceId}_ino_{base64(path)} 토큰이 필요합니다(아래 일정 자동화 참고).

구현 예제#

# --- List devices ---
curl -s "$BASE_URL/api/devices?page=1&size=20" "${AUTH[@]}"

# --- Resolve a name / IP into an exact deviceId ---
curl -s "$BASE_URL/api/devices/resolve?name=seoul-node-01" "${AUTH[@]}"

# --- Check connectivity ---
curl -s "$BASE_URL/api/devices/<DEVICE_ID>/connectivity" "${AUTH[@]}"

# --- Browse a device path (start) ---
curl -s -X POST "$BASE_URL/api/devices/<DEVICE_ID>/files/search" "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d '{"path":"C:/data/export","pageSize":500}'

# --- Browse a device path (next batch) ---
curl -s "$BASE_URL/api/devices/<DEVICE_ID>/files/search?cursor=<NEXT_CURSOR>" "${AUTH[@]}"
// --- 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;
}

// --- Resolve a name / IP into an exact deviceId ---
async function resolveDevice(name) {
  const url = new URL(`${BASE_URL}/api/devices/resolve`);
  url.searchParams.set("name", name);
  const res = await fetch(url, { headers: authHeaders() });
  if (!res.ok) throw new Error(`resolve failed: ${res.status}`);
  const data = (await res.json()).data;
  return (data.devices?.[0]?.deviceId) ?? data.deviceId;
}

// --- 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()).data.isConnected;
}

// --- Browse a device path (cursor paging) ---
async function browse(deviceId, path, pageSize = 500) {
  const items = [];
  let res = await fetch(`${BASE_URL}/api/devices/${deviceId}/files/search`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify({ path, pageSize }),
  });
  if (!res.ok) throw new Error(`browse failed: ${res.status}`);
  let page = (await res.json()).data;
  items.push(...page.items);
  while (page.hasMore) {
    const url = new URL(`${BASE_URL}/api/devices/${deviceId}/files/search`);
    url.searchParams.set("cursor", page.nextCursor);
    res = await fetch(url, { headers: authHeaders() });
    if (!res.ok) throw new Error(`browse failed: ${res.status}`);
    page = (await res.json()).data;
    items.push(...page.items);
  }
  return items;
}
# --- 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"]

# --- Resolve a name / IP into an exact deviceId ---
def resolve_device(name: str) -> str:
    res = session.get(f"{BASE_URL}/api/devices/resolve", params={"name": name}, timeout=10)
    res.raise_for_status()
    data = res.json()["data"]
    devices = data.get("devices") or []
    return devices[0]["deviceId"] if devices else data.get("deviceId")

# --- 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()["data"]["isConnected"]

# --- Browse a device path (cursor paging) ---
def browse(device_id: str, path: str, page_size: int = 500) -> list:
    items = []
    res = session.post(
        f"{BASE_URL}/api/devices/{device_id}/files/search",
        json={"path": path, "pageSize": page_size},
        timeout=15,
    )
    res.raise_for_status()
    page = res.json()["data"]
    items.extend(page["items"])
    while page["hasMore"]:
        res = session.get(
            f"{BASE_URL}/api/devices/{device_id}/files/search",
            params={"cursor": page["nextCursor"]},
            timeout=15,
        )
        res.raise_for_status()
        page = res.json()["data"]
        items.extend(page["items"])
    return items
// Methods of the InnorixClient class (use with the API 인증 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");
}

// --- Resolve a name / IP into an exact deviceId ---
public String resolveDevice(String name) throws Exception {
    String q = URLEncoder.encode(name, java.nio.charset.StandardCharsets.UTF_8);
    HttpRequest req = authed("/api/devices/resolve?name=" + q).GET().build();
    HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
    JsonNode data = mapper.readTree(res.body()).at("/data");
    JsonNode devices = data.get("devices");
    return (devices != null && devices.size() > 0)
            ? devices.get(0).get("deviceId").asText()
            : data.get("deviceId").asText();
}

// --- Check connectivity ---
public boolean 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()).at("/data/isConnected").asBoolean();
}

// --- Browse a device path (cursor paging) ---
public List<JsonNode> browse(String deviceId, String path, int pageSize) throws Exception {
    List<JsonNode> items = new ArrayList<>();
    String payload = mapper.writeValueAsString(Map.of("path", path, "pageSize", pageSize));
    HttpRequest req = authed("/api/devices/" + deviceId + "/files/search")
            .POST(HttpRequest.BodyPublishers.ofString(payload)).build();
    JsonNode page = mapper.readTree(http.send(req, HttpResponse.BodyHandlers.ofString()).body()).at("/data");
    page.get("items").forEach(items::add);
    while (page.get("hasMore").asBoolean()) {
        HttpRequest next = authed("/api/devices/" + deviceId + "/files/search?cursor="
                + URLEncoder.encode(page.get("nextCursor").asText(), java.nio.charset.StandardCharsets.UTF_8))
                .GET().build();
        page = mapper.readTree(http.send(next, HttpResponse.BodyHandlers.ofString()).body()).at("/data");
        page.get("items").forEach(items::add);
    }
    return items;
}
// Methods of the InnorixClient class (use with the API 인증 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");
}

// --- Resolve a name / IP into an exact deviceId ---
async Task<string> ResolveDeviceAsync(string name)
{
    var json = await http.GetFromJsonAsync<JsonElement>(
quot;/api/devices/resolve?name={Uri.EscapeDataString(name)}"
); var data = json.GetProperty("data"); if (data.TryGetProperty("devices", out var devices) && devices.GetArrayLength() > 0) return devices[0].GetProperty("deviceId").GetString()!; return data.GetProperty("deviceId").GetString()!; } // --- Check connectivity --- async Task<bool> IsOnlineAsync(string deviceId) { var json = await http.GetFromJsonAsync<JsonElement>(
quot;/api/devices/{deviceId}/connectivity"
); return json.GetProperty("data").GetProperty("isConnected").GetBoolean(); } // --- Browse a device path (cursor paging) --- async Task<List<JsonElement>> BrowseAsync(string deviceId, string path, int pageSize = 500) { var items = new List<JsonElement>(); var res = await http.PostAsJsonAsync(
quot;/api/devices/{deviceId}/files/search"
, new { path, pageSize }); res.EnsureSuccessStatusCode(); var page = (await res.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("data"); items.AddRange(page.GetProperty("items").EnumerateArray()); while (page.GetProperty("hasMore").GetBoolean()) { var cursor = Uri.EscapeDataString(page.GetProperty("nextCursor").GetString()!); var json = await http.GetFromJsonAsync<JsonElement>(
quot;/api/devices/{deviceId}/files/search?cursor={cursor}"
); page = json.GetProperty("data"); items.AddRange(page.GetProperty("items").EnumerateArray()); } return items; }

파일, 폴더 전송#

소스 장비의 파일을 대상 장비로 보내고, 진행 중 제어, 결과 확인을 수행합니다.

즉시 전송과 제어#

설명#

소스, 대상 장비(sourceDevice/targetDevice — deviceId, 이름, IP 모두 허용)와 평문 경로로 전송을 생성하면 monitorId가 반환됩니다(응답 data.monitorId). 폴더 루트는 sourcePaths로 넘기면 에이전트가 walk하며, 알려진 파일 목록은 sourceItem에 { path, isDir: false, fileSize }로 지정하면 더 빠릅니다. 상태는 GET /api/transfers/{monitorId}로 폴링(정수 status, percent, isTerminal)하고, pause, resume, cancel은 202로 비동기 접수됩니다. 실패 파일은 retry로 재시도하고, 파일 단위 결과는 GET /api/transfers/{monitorId}/files?idType=monitor로 조회합니다.

사용 API#

목적 Method Endpoint
전송 생성 POST /api/transfers/manual
상태 조회 GET /api/transfers/{monitorId}
전송 제어 POST /api/transfers/{monitorId}/pause, resume, cancel
실패 재시도 POST /api/transfers/{monitorId}/retry
전송 파일 조회 GET /api/transfers/{monitorId}/files

Request#

POST /api/transfers/manual

json
{
  "sourceDevice": "seoul-node-01",
  "targetDevice": "hanoi-node-02",
  "targetPath": "/data/incoming",
  "sourcePaths": ["/data/export"],
  "sendAllFolder": false,
  "transferOptions": { "target-action": "numbering" }
}

알려진 파일 목록을 정밀 지정하려면 sourcePaths 대신 sourceItem을 사용합니다: "sourceItem": [{ "path": "/data/export/report.pdf", "isDir": false, "fileSize": 20480 }]. target-action은 numbering(자동 이름변경), overwrite(덮어쓰기), nosend(동일 파일 시 미전송) 중 하나입니다.

Response#

json
{
  "statusCode": 201,
  "message": "Created",
  "data": {
    "monitorId": "D5273-6820-6280-0345",
    "transferId": "tr_5566",
    "status": 1,
    "statusName": "StartTransfer",
    "isTerminal": false
  }
}

전송 상태 코드: 0 대기, 1 시작, 2 완료, 3 일시중지, 4 오류, 5 취소, 6 전송중, 9 부분완료, 99 실패. 종료(완료 판정) 상태 = {2, 4, 5, 9, 99}.

처리 순서#

  1. POST /api/transfers/manual 호출 → data.monitorId 수신
  2. GET /api/transfers/{monitorId}로 status, percent, isTerminal 폴링
  3. 필요 시 POST /api/transfers/{monitorId}/pause, resume, cancel로 제어(202 비동기 접수), 실패는 .../retry({ filesRetry })로 재시도

구현 예제#

# --- Create a transfer ---
curl -s -X POST "$BASE_URL/api/transfers/manual" "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceDevice": "seoul-node-01",
    "targetDevice": "hanoi-node-02",
    "targetPath": "/data/incoming",
    "sourcePaths": ["/data/export"],
    "sendAllFolder": false,
    "transferOptions": { "target-action": "numbering" }
  }'

# --- Poll transfer status ---
curl -s "$BASE_URL/api/transfers/<MONITOR_ID>" "${AUTH[@]}"

# --- 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": "/data/export/file.txt", "isFolder": false } ] }'
const TERMINAL_TRANSFER_STATUSES = new Set([2, 4, 5, 9, 99]);

// --- Create a transfer ---
async function createTransfer(sourceDevice, targetDevice, targetPath, sourcePaths, sendAllFolder = false) {
  const res = await fetch(`${BASE_URL}/api/transfers/manual`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify({ sourceDevice, targetDevice, targetPath, sourcePaths, sendAllFolder }),
  });
  if (!res.ok) throw new Error(`transfer failed: ${res.status}`);
  return (await res.json()).data.monitorId;
}

// --- Poll transfer status ---
async function waitForCompletion(monitorId, intervalMs = 2000) {
  for (;;) {
    const res = await fetch(`${BASE_URL}/api/transfers/${monitorId}`, { headers: authHeaders() });
    if (!res.ok) throw new Error(`status failed: ${res.status}`);
    const detail = (await res.json()).data;
    const isTerminal = detail.isTerminal ?? TERMINAL_TRANSFER_STATUSES.has(detail.status);
    console.log("transfer status:", detail.status, detail.statusName, `${detail.percent || 0}%`);
    if (isTerminal) {
      if (detail.status !== 2) throw new Error(detail.errorCode || "Transfer failed");
      return detail.status;
    }
    await new Promise((r) => setTimeout(r, intervalMs));
  }
}

// --- Control a transfer (pause / resume / cancel) — 202 accepted (async) ---
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}`);
}
TERMINAL_TRANSFER_STATUSES = {2, 4, 5, 9, 99}

# --- Create a transfer ---
def create_transfer(source_device: str, target_device: str, target_path: str,
                    source_paths: list, send_all_folder: bool = False) -> str:
    res = session.post(
        f"{BASE_URL}/api/transfers/manual",
        json={
            "sourceDevice": source_device,
            "targetDevice": target_device,
            "targetPath": target_path,
            "sourcePaths": source_paths,
            "sendAllFolder": send_all_folder,
        },
        timeout=15,
    )
    res.raise_for_status()
    return res.json()["data"]["monitorId"]

# --- Poll transfer status ---
def wait_for_completion(monitor_id: str, interval: float = 2.0) -> int:
    while True:
        res = session.get(f"{BASE_URL}/api/transfers/{monitor_id}", timeout=10)
        res.raise_for_status()
        detail = res.json()["data"]
        is_terminal = detail.get("isTerminal", detail.get("status") in TERMINAL_TRANSFER_STATUSES)
        print("transfer status:", detail.get("status"), detail.get("statusName"), f"{detail.get('percent', 0)}%")
        if is_terminal:
            if detail.get("status") != 2:
                raise RuntimeError(detail.get("errorCode") or "Transfer failed")
            return detail.get("status")
        time.sleep(interval)

# --- Control a transfer (pause / resume / cancel) — 202 accepted (async) ---
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()
// Methods of the InnorixClient class (use with the API 인증 code)
static final Set<Integer> TERMINAL_TRANSFER_STATUSES = Set.of(2, 4, 5, 9, 99);

// --- Create a transfer ---
public String createTransfer(String sourceDevice, String targetDevice, String targetPath,
                             List<String> sourcePaths, boolean sendAllFolder) throws Exception {
    Map<String, Object> body = new LinkedHashMap<>();
    body.put("sourceDevice", sourceDevice);
    body.put("targetDevice", targetDevice);
    body.put("targetPath", targetPath);
    body.put("sourcePaths", sourcePaths);
    body.put("sendAllFolder", sendAllFolder);

    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();
}

// --- Poll transfer status ---
public int waitForCompletion(String monitorId, long intervalMs) throws Exception {
    while (true) {
        HttpRequest req = authed("/api/transfers/" + monitorId).GET().build();
        JsonNode detail = mapper.readTree(http.send(req, HttpResponse.BodyHandlers.ofString()).body()).at("/data");
        int status = detail.get("status").asInt();
        boolean isTerminal = detail.has("isTerminal")
                ? detail.get("isTerminal").asBoolean()
                : TERMINAL_TRANSFER_STATUSES.contains(status);
        System.out.println("transfer status: " + status + " " + detail.path("statusName").asText());
        if (isTerminal) {
            if (status != 2) throw new RuntimeException(detail.path("errorCode").asText("Transfer failed"));
            return status;
        }
        Thread.sleep(intervalMs);
    }
}

// --- Control a transfer (pause / resume / cancel) — 202 accepted (async) ---
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 = Map.of("filesRetry", files);
    HttpRequest req = authed("/api/transfers/" + monitorId + "/retry")
            .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body))).build();
    http.send(req, HttpResponse.BodyHandlers.ofString());
}
// Methods of the InnorixClient class (use with the API 인증 code)
static readonly HashSet<int> TerminalTransferStatuses = new() { 2, 4, 5, 9, 99 };

// --- Create a transfer ---
async Task<string> CreateTransferAsync(string sourceDevice, string targetDevice, string targetPath,
    IEnumerable<string> sourcePaths, bool sendAllFolder = false)
{
    var res = await http.PostAsJsonAsync("/api/transfers/manual",
        new { sourceDevice, targetDevice, targetPath, sourcePaths, sendAllFolder });
    res.EnsureSuccessStatusCode();
    var json = await res.Content.ReadFromJsonAsync<JsonElement>();
    return json.GetProperty("data").GetProperty("monitorId").GetString()!;
}

// --- Poll transfer status ---
async Task<int> WaitForCompletionAsync(string monitorId, int intervalMs = 2000)
{
    while (true)
    {
        var json = await http.GetFromJsonAsync<JsonElement>(
quot;/api/transfers/{monitorId}"
); var detail = json.GetProperty("data"); var status = detail.GetProperty("status").GetInt32(); var isTerminal = detail.TryGetProperty("isTerminal", out var t) ? t.GetBoolean() : TerminalTransferStatuses.Contains(status); Console.WriteLine(
quot;transfer status: {status}"
); if (isTerminal) { if (status != 2) throw new Exception("Transfer failed"); return status; } await Task.Delay(intervalMs); } } // --- Control a transfer (pause / resume / cancel) — 202 accepted (async) --- 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(); }

일정 자동화#

정해진 시간에 반복 실행되는 예약 전송을 구성합니다.

반복 자동화#

설명#

스케줄과 전송 상세(details)를 담아 자동화를 생성하면 automationId가 반환되며, 이 값으로 조회, 일시정지, 재개, 수정, 삭제합니다. 반복 스케줄은 transferType을 normal로 두고 schedules[].type을 day, week, month 등으로 지정하며, 시각은 12시간제 hour("01"–"12"), minute, ampm(am/pm)로 표현합니다. details[].sourceItem은 즉시 전송과 달리 {deviceId}_ino_{base64(path)} 토큰(hash 필드)이 필요하고, senderId/receiverId는 deviceId입니다. 실행 이력, 상태는 GET /api/automations/{automationId}/executions로 확인합니다.

사용 API#

목적 Method Endpoint
자동화 생성 POST /api/automations
자동화 조회 GET /api/automations/{automationId}
실행 이력 조회 GET /api/automations/{automationId}/executions
자동화 일시정지 POST /api/automations/{automationId}/pause
자동화 수정 PATCH /api/automations/{automationId}
자동화 삭제 DELETE /api/automations/{automationId}

Request#

POST /api/automations

json
{
  "name": "Daily Settlement Transfer",
  "transferType": "normal",
  "timezone": "Asia/Seoul",
  "schedules": [
    {
      "type": "day",
      "startDateType": "now",
      "hour": "02",
      "minute": "00",
      "ampm": "am",
      "startDate": "2026-01-01T00:00:00.000Z",
      "timezone": "Asia/Seoul"
    }
  ],
  "details": [
    {
      "senderId": "6901ae48ca578216fd739f78",
      "receiverId": "690037c22d309a7bc494bc53",
      "sourceItem": [
        { "hash": "6901ae48ca578216fd739f78_ino_L2RhdGEvcmVwb3J0LnBkZg==", "isDir": false }
      ],
      "targetPath": "/data/incoming",
      "step": 1,
      "transferOptions": { "target-action": "numbering" }
    }
  ]
}

hash 토큰은 <deviceId>_ino_<base64(path)> 형식입니다. 예: deviceId가 6901ae48ca578216fd739f78이고 경로가 /data/report.pdf이면, 경로를 base64로 인코딩(L2RhdGEvcmVwb3J0LnBkZg==)해 붙입니다. type은 week이면 dayInWeek(요일명 배열), month이면 dayInMonth(일자 배열)가 추가로 필요합니다.

Response#

json
{
  "statusCode": 200,
  "message": "success",
  "data": { "automationId": "auto_301" }
}

처리 순서#

  1. POST /api/automations 호출 → data.automationId 수신
  2. GET /api/automations/{automationId}/executions로 실행 이력, 상태 확인
  3. POST /api/automations/{automationId}/pause({ "pause": true })로 일시정지/재개, PATCH({ isUpdateSchedule, schedules }), DELETE로 수정, 삭제

구현 예제#

# --- Build the `<deviceId>_ino_<base64(path)>` token ---
DEVICE_ID="6901ae48ca578216fd739f78"
TOKEN="${DEVICE_ID}_ino_$(printf '%s' '/data/report.pdf' | base64)"

# --- Create an automation (every day at 02:00 AM) ---
curl -s -X POST "$BASE_URL/api/automations" "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Daily Settlement Transfer",
    "transferType": "normal",
    "timezone": "Asia/Seoul",
    "schedules": [
      { "type": "day", "startDateType": "now", "hour": "02", "minute": "00", "ampm": "am",
        "startDate": "2026-01-01T00:00:00.000Z", "timezone": "Asia/Seoul" }
    ],
    "details": [
      {
        "senderId": "6901ae48ca578216fd739f78",
        "receiverId": "690037c22d309a7bc494bc53",
        "sourceItem": [ { "hash": "'"$TOKEN"'", "isDir": false } ],
        "targetPath": "/data/incoming",
        "step": 1,
        "transferOptions": { "target-action": "numbering" }
      }
    ]
  }'

# --- Read / list executions ---
curl -s "$BASE_URL/api/automations/<AUTOMATION_ID>" "${AUTH[@]}"
curl -s "$BASE_URL/api/automations/<AUTOMATION_ID>/executions" "${AUTH[@]}"

# --- Pause / resume ---
curl -s -X POST "$BASE_URL/api/automations/<AUTOMATION_ID>/pause" "${AUTH[@]}" \
  -H "Content-Type: application/json" -d '{"pause": true}'

# --- Update / delete ---
curl -s -X PATCH "$BASE_URL/api/automations/<AUTOMATION_ID>" "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Daily Settlement Transfer", "isUpdateSchedule": true, "schedules": [] }'
curl -s -X DELETE "$BASE_URL/api/automations/<AUTOMATION_ID>" "${AUTH[@]}"
// --- Build the `<deviceId>_ino_<base64(path)>` token ---
function encodePath(deviceId, rawPath) {
  const normalized = String(rawPath || "").replaceAll("\\", "/");
  return `${deviceId}_ino_${Buffer.from(normalized, "utf8").toString("base64")}`;
}

// --- Create an automation (every day at 02:00 AM) ---
async function createAutomation({ name, senderId, receiverId, sourcePath, targetPath, timezone }) {
  const body = {
    name,
    transferType: "normal",
    timezone,
    schedules: [
      { type: "day", startDateType: "now", hour: "02", minute: "00", ampm: "am",
        startDate: new Date().toISOString(), timezone },
    ],
    details: [
      {
        senderId,
        receiverId,
        sourceItem: [{ hash: encodePath(senderId, sourcePath), isDir: false }],
        targetPath,
        step: 1,
        transferOptions: { "target-action": "numbering" },
      },
    ],
  };
  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;
}

// --- Read / list executions ---
async function getAutomation(id) {
  const res = await fetch(`${BASE_URL}/api/automations/${id}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`read failed: ${res.status}`);
  return (await res.json()).data;
}
async function listExecutions(id) {
  const res = await fetch(`${BASE_URL}/api/automations/${id}/executions`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`executions failed: ${res.status}`);
  return (await res.json()).data;
}

// --- Pause / resume ---
async function pauseAutomation(id, pause = true) {
  const res = await fetch(`${BASE_URL}/api/automations/${id}/pause`, {
    method: "POST", headers: authHeaders(), body: JSON.stringify({ pause }),
  });
  if (!res.ok) throw new Error(`pause failed: ${res.status}`);
}

// --- Update / delete ---
async function updateAutomation(id, body) {
  const res = await fetch(`${BASE_URL}/api/automations/${id}`, {
    method: "PATCH", headers: authHeaders(), body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`update failed: ${res.status}`);
}
async function deleteAutomation(id) {
  const res = await fetch(`${BASE_URL}/api/automations/${id}`, { method: "DELETE", headers: authHeaders() });
  if (!res.ok) throw new Error(`delete failed: ${res.status}`);
}
import base64
from datetime import datetime, timezone as _tz

# --- Build the `<deviceId>_ino_<base64(path)>` token ---
def encode_path(device_id: str, raw_path: str) -> str:
    normalized = str(raw_path or "").replace("\\", "/")
    token = base64.b64encode(normalized.encode("utf-8")).decode("ascii")
    return f"{device_id}_ino_{token}"

def _now_iso() -> str:
    return datetime.now(_tz.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"

# --- Create an automation (every day at 02:00 AM) ---
def create_automation(name, sender_id, receiver_id, source_path, target_path, tz="Asia/Seoul"):
    body = {
        "name": name,
        "transferType": "normal",
        "timezone": tz,
        "schedules": [
            {"type": "day", "startDateType": "now", "hour": "02", "minute": "00",
             "ampm": "am", "startDate": _now_iso(), "timezone": tz}
        ],
        "details": [
            {
                "senderId": sender_id,
                "receiverId": receiver_id,
                "sourceItem": [{"hash": encode_path(sender_id, source_path), "isDir": False}],
                "targetPath": target_path,
                "step": 1,
                "transferOptions": {"target-action": "numbering"},
            }
        ],
    }
    res = session.post(f"{BASE_URL}/api/automations", json=body, timeout=15)
    res.raise_for_status()
    return res.json()["data"]["automationId"]

# --- Read / list executions ---
def get_automation(automation_id):
    res = session.get(f"{BASE_URL}/api/automations/{automation_id}", timeout=10)
    res.raise_for_status()
    return res.json()["data"]

def list_executions(automation_id):
    res = session.get(f"{BASE_URL}/api/automations/{automation_id}/executions", timeout=10)
    res.raise_for_status()
    return res.json()["data"]

# --- Pause / resume ---
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 ---
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 InnorixClient class (use with the API 인증 code)

// --- Build the `<deviceId>_ino_<base64(path)>` token ---
public String encodePath(String deviceId, String rawPath) {
    String normalized = rawPath == null ? "" : rawPath.replace("\\", "/");
    String token = Base64.getEncoder().encodeToString(normalized.getBytes(java.nio.charset.StandardCharsets.UTF_8));
    return deviceId + "_ino_" + token;
}

// --- Create an automation (every day at 02:00 AM) ---
public String createAutomation(String name, String senderId, String receiverId,
                               String sourcePath, String targetPath, String tz) throws Exception {
    Map<String, Object> schedule = new LinkedHashMap<>();
    schedule.put("type", "day");
    schedule.put("startDateType", "now");
    schedule.put("hour", "02");
    schedule.put("minute", "00");
    schedule.put("ampm", "am");
    schedule.put("startDate", java.time.Instant.now().toString());
    schedule.put("timezone", tz);

    Map<String, Object> item = Map.of("hash", encodePath(senderId, sourcePath), "isDir", false);
    Map<String, Object> detail = new LinkedHashMap<>();
    detail.put("senderId", senderId);
    detail.put("receiverId", receiverId);
    detail.put("sourceItem", List.of(item));
    detail.put("targetPath", targetPath);
    detail.put("step", 1);
    detail.put("transferOptions", Map.of("target-action", "numbering"));

    Map<String, Object> body = new LinkedHashMap<>();
    body.put("name", name);
    body.put("transferType", "normal");
    body.put("timezone", tz);
    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();
}

// --- Read / list executions ---
public JsonNode getAutomation(String id) throws Exception {
    HttpRequest req = authed("/api/automations/" + id).GET().build();
    return mapper.readTree(http.send(req, HttpResponse.BodyHandlers.ofString()).body()).at("/data");
}
public JsonNode listExecutions(String id) throws Exception {
    HttpRequest req = authed("/api/automations/" + id + "/executions").GET().build();
    return mapper.readTree(http.send(req, HttpResponse.BodyHandlers.ofString()).body()).at("/data");
}

// --- Pause / resume ---
public void pauseAutomation(String id, boolean pause) throws Exception {
    String payload = mapper.writeValueAsString(Map.of("pause", pause));
    HttpRequest req = authed("/api/automations/" + id + "/pause")
            .POST(HttpRequest.BodyPublishers.ofString(payload)).build();
    http.send(req, HttpResponse.BodyHandlers.ofString());
}

// --- Update / delete ---
public void updateAutomation(String id, Map<String, Object> body) throws Exception {
    HttpRequest req = authed("/api/automations/" + id)
            .method("PATCH", HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body))).build();
    http.send(req, HttpResponse.BodyHandlers.ofString());
}
public void deleteAutomation(String id) throws Exception {
    HttpRequest req = authed("/api/automations/" + id).DELETE().build();
    http.send(req, HttpResponse.BodyHandlers.ofString());
}
// Methods of the InnorixClient class (use with the API 인증 code)

// --- Build the `<deviceId>_ino_<base64(path)>` token ---
string EncodePath(string deviceId, string rawPath)
{
    var normalized = (rawPath ?? "").Replace("\\", "/");
    var token = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(normalized));
    return 
quot;{deviceId}_ino_{token}"
; } // --- Create an automation (every day at 02:00 AM) --- async Task<string> CreateAutomationAsync(string name, string senderId, string receiverId, string sourcePath, string targetPath, string tz = "Asia/Seoul") { var body = new { name, transferType = "normal", timezone = tz, schedules = new[] { new { type = "day", startDateType = "now", hour = "02", minute = "00", ampm = "am", startDate = DateTime.UtcNow.ToString("o"), timezone = tz } }, details = new[] { new { senderId, receiverId, sourceItem = new[] { new { hash = EncodePath(senderId, sourcePath), isDir = false } }, targetPath, step = 1, transferOptions = new Dictionary<string, string> { ["target-action"] = "numbering" }, } } }; var res = await http.PostAsJsonAsync("/api/automations", body); res.EnsureSuccessStatusCode(); var json = await res.Content.ReadFromJsonAsync<JsonElement>(); return json.GetProperty("data").GetProperty("automationId").GetString()!; } // --- Read / list executions --- async Task<JsonElement> GetAutomationAsync(string id) => (await http.GetFromJsonAsync<JsonElement>(
quot;/api/automations/{id}"
)).GetProperty("data"); async Task<JsonElement> ListExecutionsAsync(string id) => (await http.GetFromJsonAsync<JsonElement>(
quot;/api/automations/{id}/executions"
)).GetProperty("data"); // --- Pause / resume --- async Task PauseAutomationAsync(string id, bool pause = true) { var res = await http.PostAsJsonAsync(
quot;/api/automations/{id}/pause"
, new { pause }); res.EnsureSuccessStatusCode(); } // --- Update / delete --- async Task UpdateAutomationAsync(string id, object body) { var res = await http.PatchAsJsonAsync(
quot;/api/automations/{id}"
, body); res.EnsureSuccessStatusCode(); } async Task DeleteAutomationAsync(string id) { var res = await http.DeleteAsync(
quot;/api/automations/{id}"
); res.EnsureSuccessStatusCode(); }
다음AI 모델 파일을 여러 엣지 장비에 배포하기

이 페이지에서

  • API 인증
  • 로그인과 토큰
  • 시스템 연결
  • 디바이스 확인과 파일 탐색
  • 파일, 폴더 전송
  • 즉시 전송과 제어
  • 일정 자동화
  • 반복 자동화