한 곳으로 파일 보내기

INNORIX Transfer Builder를 사용해 1대1 파일 전송을 구성합니다. 하나의 소스와 하나의 대상을 선택하고 전송 설정을 생성합니다.

한 곳으로 파일 보내기는 보내는 디바이스 한 대(Source)에서 받는 디바이스 한 대(Target)로 파일 또는 폴더를 전송하는, 가장 기본이 되는 형태입니다.

API 요청은 POST /api/automations 한 번입니다. details 에 보내는 쪽과 받는 쪽 한 쌍을 넣고, schedules 로 실행 시점을 정합니다.

전송 빌더에서 Get API Code 를 누르면 같은 내용이 언어별 실행 가능한 예제(combo_builder.*)와 .env 로 묶여 내려받아집니다. 이 문서는 그 예제에서 핵심만 떼어낸 것입니다.

시작하기#

준비물#

  1. API Key — 제품 좌측 하단 프로필 메뉴 → Developer 에서 발급합니다. 같은 화면에 Workspace ID 도 함께 표시됩니다. API 로 발급하려면 POST /api/auth/api-keys (Bearer 액세스 토큰, 바디 없음) → data.apiKey.
  2. deviceId 두 개 — 제품의 Devices 에서 디바이스를 선택하면 우측 상단에 표시되는 ID 입니다. Windows, macOS, Ubuntu, RHEL, Rocky, Debian 등 에이전트가 설치된 장비와 Amazon S3, Azure Blob, Google Cloud Storage 같은 오브젝트 스토리지를 모두 지정할 수 있습니다.
  3. 경로 — 보낼 쪽 경로(sourceItem[].filePath)와 받을 쪽 경로(targetPath). 둘 다 슬래시(/)로 구분한 절대 경로를 사용합니다. targetPath 는 비어 있거나 / 이면 안 됩니다.

인증은 다음 두 가지 중 하나를 사용합니다.

http
x-api-key: <API Key>                  # long-lived key (recommended)
Authorization: Bearer <accessToken>   # short-lived token from login

워크스페이스를 명시해야 하는 경우에만 헤더를 하나 더 추가합니다. 이 헤더는 인증 수단이 아니라 대상 워크스페이스 지정용입니다.

http
x-workspace-id: <Workspace ID>        # optional

기본 주소는 https://app.innorix.com 입니다.

빠른 시작#

빌더에서 Get API Code 로 받은 번들을 그대로 실행하는 순서입니다.

  1. 전송 빌더에서 옵션을 고르고 Get API Code → 언어 선택 → zip 다운로드
  2. 압축을 풀고 .env 를 열어 INNORIX_API_KEY 와 SOURCE_ID, TARGET_ID, 경로(SOURCE_PATH, TARGET_PATH)를 채웁니다
  3. 아래 명령으로 실행합니다
  4. 출력된 automationId 로 전송 상태를 조회합니다
언어 요구 사항 실행
Python Python 3.8+ pip install requests → python combo_builder.py
Node.js Node.js 18+ (의존성 없음) node combo_builder.js
Java JDK 11+ (의존성 없음) java ComboBuilder.java 또는 javac ComboBuilder.java && java ComboBuilder
C# .NET 8+ dotnet run

ℹ️ 위 요구 사항은 번들 예제 기준입니다. 이 문서에 실린 Java 발췌 코드는 가독성을 위해 텍스트 블록(""")을 사용하므로 JDK 17+ 가 필요합니다. 번들의 ComboBuilder.java 는 JDK 11+ 에서 동작합니다.

ℹ️ 번들의 combo_builder.* 는 같은 폴더의 .env 를 직접 읽습니다(별도 라이브러리 없이). 반면 이 문서에 실린 발췌 코드는 환경 변수에서 값을 읽으므로, 그대로 복사해 실행할 때는 아래처럼 값을 내보낸 뒤 실행하세요.

macOS, Linux

bash
export INNORIX_API_KEY=your-api-key
export SOURCE_ID=device-source-01
export SOURCE_PATH=C:/data/out
export TARGET_ID=device-target-01
export TARGET_PATH=C:/incoming

Windows PowerShell (CMD 에서는 set INNORIX_API_KEY=your-api-key 형식)

powershell
$env:INNORIX_API_KEY="your-api-key"
$env:SOURCE_ID="device-source-01"
$env:SOURCE_PATH="C:/data/out"
$env:TARGET_ID="device-target-01"
$env:TARGET_PATH="C:/incoming"

전송 만들기#

전송 만들기#

한 대에서 한 대로 보내기는 POST /api/automations 한 번으로 만들어집니다. details 배열에 보내는 쪽 / 받는 쪽 한 쌍을 넣고, schedules 에 실행 시점을 넣습니다. 아래는 지금 바로 실행하는 경우입니다.

json
{
  "name": "nightly-export",
  "flowName": "nightly-export",
  "transferType": "normal",
  "timezone": "Asia/Seoul",
  "details": [
    {
      "senderId": "<sourceDeviceId>",
      "receiverId": "<targetDeviceId>",
      "sourceItem": [{ "filePath": "C:/data/out", "isDir": true }],
      "targetPath": "C:/incoming",
      "step": 1,
      "transferOptions": { "noSchedule": false, "target-action": "overwrite" }
    }
  ],
  "schedules": [
    { "type": "none", "startDateType": "now", "startDate": "2026-09-14T02:00:00.000Z", "timezone": "Asia/Seoul" }
  ],
  "step": 1,
  "isUpcoming": false
}
  • transferOptions.target-action 은 이름이 겹칠 때의 동작입니다. overwrite(덮어쓰기), numbering(이름 뒤에 번호), nosend(건너뛰기) 중 하나를 넣습니다.
  • startDate 는 예시 값입니다. Now 로 실행할 때는 요청 시점의 현재 UTC 시각을 넣으세요 (아래 예제 코드는 실행할 때마다 현재 시각을 계산합니다).

응답의 data.automationId 가 이후 조회에 쓰이는 식별자입니다.

# pip install requests
import os, time, requests

BASE = os.getenv("INNORIX_BASE_URL", "https://app.innorix.com")
HEADERS = {"x-api-key": os.environ["INNORIX_API_KEY"], "Content-Type": "application/json"}

# Every setting comes from an environment variable (second argument is the default).
SOURCE_ID     = os.environ["SOURCE_ID"]
SOURCE_PATH   = os.getenv("SOURCE_PATH", "C:/data/out")
TARGET_ID     = os.environ["TARGET_ID"]
TARGET_PATH   = os.getenv("TARGET_PATH", "C:/incoming")
SOURCE_IS_DIR = os.getenv("SOURCE_IS_DIR", "true").lower() != "false"
TZ            = os.getenv("SCHEDULE_TZ", "Asia/Seoul")

def now_iso():
    return time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())

def call(method, path, body=None, params=None):
    r = requests.request(method, BASE + path, headers=HEADERS,
                         json=body, params=params, timeout=30)
    if not r.ok:
        raise RuntimeError(f"API {r.status_code}: {r.text[:500]}")
    return (r.json() or {}).get("data")

body = {
    "name": "nightly-export",
    "flowName": "nightly-export",
    "transferType": "normal",          # "sync" for sync, "command" for an external trigger
    "timezone": TZ,
    "details": [{
        "senderId": SOURCE_ID,
        "receiverId": TARGET_ID,
        "sourceItem": [{"filePath": SOURCE_PATH, "isDir": SOURCE_IS_DIR}],   # false for a single file
        "targetPath": TARGET_PATH,
        "step": 1,
        "transferOptions": {"noSchedule": False, "target-action": "overwrite"},
    }],
    "schedules": [{
        "type": "none", "startDateType": "now",
        "startDate": now_iso(), "timezone": TZ,
    }],
    "step": 1,
    "isUpcoming": False,
}

automation_id = call("POST", "/api/automations", body)["automationId"]
print("automation created:", automation_id)

진행 상황 확인#

자동화가 만들어지면 실제 전송은 별도의 monitorId 로 추적합니다.

  1. GET /api/transfers?automationId=<automationId> — 진행 중인 전송 목록. 응답은 커서 페이지네이션(data.data[])이고, 실제 전송 행은 type: "monitor" 입니다. type 이 automation, history, flow 인 행은 요약 행이므로 건너뜁니다.
  2. GET /api/transfers/<monitorId> — 상태와 진행률.

상태 코드는 다음과 같습니다.

코드 의미 코드 의미
-1 queued 6 transferring
0 waiting 7 skipped
1 started 8 retry
2 complete 9 partial-complete
3 paused 11 virus-scanning
4 error 12 syncing
5 cancelled 99 fail

종료 상태는 2, 4, 5, 9, 99 입니다.

SKIP_ROW_TYPES = {"automation", "history", "flow"}
TERMINAL = {2, 4, 5, 9, 99}
STATUS = {-1: "queued", 0: "waiting", 1: "started", 2: "complete", 3: "paused",
          4: "error", 5: "cancelled", 6: "transferring", 7: "skipped", 8: "retry",
          9: "partial-complete", 11: "virus-scanning", 12: "syncing", 99: "fail"}

def monitor_ids(automation_id):
    result = call("GET", "/api/transfers", params={"automationId": automation_id})
    records = result.get("data") if isinstance(result, dict) else result
    return [r.get("monitorId") or r.get("id")
            for r in (records or []) if r.get("type") not in SKIP_ROW_TYPES]

def wait_for(monitor_id, timeout=3600):
    deadline = time.time() + timeout
    while time.time() < deadline:
        detail = call("GET", f"/api/transfers/{monitor_id}") or {}
        status = detail.get("status")
        print(f"  {STATUS.get(status, status)} ({detail.get('percent', 0)}%)")
        if detail.get("isTerminal", status in TERMINAL):
            return detail
        time.sleep(3)
    raise TimeoutError(f"{monitor_id} did not finish within {timeout}s")

for mid in monitor_ids(automation_id):
    wait_for(mid)

전송 옵션#

시작 시점#

실행 시점은 schedules[0] 하나로 정합니다. details 는 그대로 두고 이 객체만 바꾸면 됩니다.

실행 시점 schedules[0] 비고
지금 바로 { type: "none", startDateType: "now", startDate: <현재 ISO> } 생성 즉시 실행
지정 시각에 1회 { type: "none", startDateType: "specific", startDate: "2026-09-20T01:00:00" }
매시 반복 { type: "hour", ... } 매시 정각
매일 반복 { type: "day", hour, minute, ampm }
매주 반복 { type: "week", dayInWeek: ["monday"], hour, minute, ampm }
매월 반복 { type: "month", dayInMonth: ["1"], hour, minute, ampm } 0 은 말일
이전 자동화가 끝난 뒤 { type: "none", startDateType: "now", triggerAutomation: { value: "<이전 automationId>" } } 바디에 flowId 추가
외부 요청으로 { type: "none", startDateType: "now" } + 바디 transferType: "command" 아래 참고

반복(repeat)에서는 startDateType / startDate 로 첫 실행 시각을 정합니다. startDateType: "now" 면 생성 즉시 한 번 실행하고 이후 주기대로, "specific" 이면 계산한 다음 주기부터 시작합니다. hour 는 1–12, ampm 은 am / pm, timezone 은 Asia/Seoul 같은 IANA 이름입니다.

External request 는 두 번의 사전 호출이 필요합니다.

http
POST /api/command/generate-code       → data.code
GET  /api/command/generate-api-key    → data.apiKey

두 값을 자동화 바디의 code, apiKey 로 넣어 생성하면, 다음 주소를 호출할 때마다 전송이 시작됩니다.

http
POST https://app.innorix.com/command/<code>
x-api-key: <apiKey>

파일 옵션#

파일 처리 옵션은 details[].transferOptions 안에 넣습니다.

옵션 키 값
확장자 필터 send-fileoption.extension { "extension": ["pdf","mp4"], "allow": true } — 차단 목록이면 allow: false
크기 필터 send-fileoption.fileSize { "size": <바이트>, "over": true, "equal": true } — 하한은 over: true, 상한은 over: false (한쪽만 지정 가능)
이름 필터 send-fileoption.fileName { "name": "temp", "allow": false } — 이름에 포함되면 제외
폴더 구조 유지 savepath true
날짜 하위 폴더 savepath + optionPath true + 1
디바이스명 하위 폴더 savepath + optionPath true + 2
사용자 지정 하위 폴더 savepath + optionPath "<폴더명>" + 3
중복 이름 — 덮어쓰기 target-action "overwrite"
중복 이름 — 이름 뒤에 번호 target-action "numbering"
중복 이름 — 건너뛰기 target-action "nosend"
무결성 검증 checkIntegrity true
json
{
  "noSchedule": false,
  "target-action": "numbering",
  "checkIntegrity": true,
  "savepath": true,
  "optionPath": 1,
  "send-fileoption": {
    "extension": { "extension": ["pdf", "xlsx"], "allow": true },
    "fileSize": { "size": 1048576, "over": true, "equal": true },
    "fileName": { "name": "tmp", "allow": false }
  }
}

ℹ️ optionPath(날짜, 디바이스명, 사용자 지정 하위 폴더)는 자동화에서만 적용됩니다. 이 문서의 모든 전송은 POST /api/automations 로 만들어지므로 Start → Now 여도 그대로 적용됩니다.

전송 후 동작#

전송이 끝난 뒤의 동작은 두 갈래로 나뉩니다.

① 자동화에 붙는 프로세서 — 바디의 processors[]

json
{
  "processors": [
    { "events": "Run", "type": "https", "method": "POST",
      "url": "https://api.example.com/webhook", "body": "{\"event\":\"done\"}" },
    { "category": "monitoring", "type": "grafana", "name": "builder-grafana",
      "config": { "baseUrl": "https://grafana.company.com", "apiToken": "***" },
      "notificationConfig": { "events": { "completed": true, "error": true } } }
  ]
}
  • Run API — 전송마다 호출되는 HTTP 훅입니다.
  • Monitoring(Grafana, Datadog, Prometheus 등) — 워크스페이스 전역이 아니라 이 자동화에 붙습니다. 선택 가능한 이벤트는 started, completed, paused, recovered, deviceConnected, deviceDisconnected 입니다.

② 워크스페이스 전역 연동 — POST /api/integrations

Message(Slack, Teams, Discord …), Virus scan(ClamAV, Microsoft Defender …), Email(SES, SendGrid) 은 전송이 아니라 워크스페이스에 등록됩니다.

json
{
  "name": "builder-slack",
  "type": "slack",
  "category": "notification",
  "config": { "webhookUrl": "https://hooks.slack.com/services/XXX", "channel": "#transfers" },
  "notificationConfig": { "events": { "completed": true, "error": true } }
}

category 는 Message → notification, Virus scan → security, Email → email 입니다. 제공자별 필수 설정 항목은 GET /api/integrations/rules/{type} 으로 확인할 수 있습니다. 이벤트 이름은 started, completed, paused, resumed, recovered, canceled, error, skipped 입니다.

참고#

빌더 UI ↔ .env ↔ API 매핑#

Get API Code 로 받은 번들의 .env 키와 API 필드의 대응입니다.

빌더 UI .env API
탭 = 한 대에서 한 대로 보내기 TRANSFER_TYPE=send_one details 1개
From 디바이스 SOURCE_ID details[].senderId
From 경로 SOURCE_PATH details[].sourceItem[].filePath
폴더/파일 여부 SOURCE_IS_DIR details[].sourceItem[].isDir
To 디바이스 TARGET_ID details[].receiverId
To 경로 TARGET_PATH details[].targetPath
Start START_WHEN schedules[0]
전송 이름 NAME name, flowName
File options FILTER_*, SAVE_PATH, DUPLICATE_ACTION, INTEGRITY details[].transferOptions
After transfer ON_* processors[], POST /api/integrations

자주 겪는 오류#

증상 원인과 해결
401 Unauthorized x-api-key 가 비었거나 만료됐습니다. Developer 화면에서 재발급하세요. Bearer 토큰은 수명이 짧습니다.
400 Bad Request targetPath 가 비었거나 / 인 경우가 가장 많습니다. senderId, receiverId 의 deviceId 오타도 확인하세요.
자동화는 만들어졌는데 전송이 안 보임 에이전트가 오프라인일 수 있습니다. GET /api/transfers?automationId=... 을 몇 초 간격으로 다시 조회하세요.
진행률이 멈춰 있음 받는 쪽 에이전트 연결이 끊겼을 때 나타납니다. 디바이스 상태를 먼저 확인하세요.
필터를 걸었는데 다 전송됨 send-fileoption.extension 은 { "extension": [...], "allow": ... } 중첩 구조입니다. 배열만 넣으면 무시됩니다.

요청과 응답을 그대로 보고 싶으면 내려받은 예제에서 DEBUG=true 를 켜면 됩니다.