Sync 는 폴더를 감시하다가 파일이 생기거나 바뀌면 자동으로 전송합니다. 한 번 만들어 두면 계속 동작하는 상주형 자동화이며, 요청에서 눈여겨볼 점은 두 가지입니다.
- 바디의
transferType이"sync"입니다. - 사용자가 시작 시점을 선택하지 않습니다. 다만 API 요청 형식상
schedules필드는 필요하므로now기본값을 그대로 전달합니다. 실제 실행 여부는 스케줄이 아니라 폴더 감시가 결정합니다.
시작하기
준비물
- API Key — 제품 좌측 하단 프로필 메뉴 → Developer 에서 발급합니다. 같은 화면에 Workspace ID 도 함께 표시됩니다. API 로 발급하려면
POST /api/auth/api-keys(Bearer 액세스 토큰, 바디 없음) →data.apiKey. - deviceId 2개 — 감시할 쪽(Source)과 반영될 쪽(Target). 제품의 Devices 에서 디바이스를 선택하면 우측 상단에 표시되는 ID 입니다.
- 경로 — 감시할 폴더(
sourceItem[].filePath)와 반영될 폴더(targetPath). 둘 다 슬래시(/)로 구분한 절대 경로를 쓰고, 감시 대상은 반드시 폴더이므로isDir: true입니다.targetPath는 비어 있거나/이면 안 됩니다.
인증은 다음 두 가지 중 하나를 사용합니다.
x-api-key: <API Key> # long-lived key (recommended)
Authorization: Bearer <accessToken> # short-lived token from login
워크스페이스를 명시해야 하는 경우에만 헤더를 하나 더 추가합니다. 이 헤더는 인증 수단이 아니라 대상 워크스페이스 지정용입니다.
x-workspace-id: <Workspace ID> # optional
기본 주소는 https://app.innorix.com 입니다.
빠른 시작
빌더에서 Get API Code 로 받은 번들을 그대로 실행하는 순서입니다.
- 전송 빌더에서 옵션을 고르고 Get API Code → 언어 선택 → zip 다운로드
- 압축을 풀고
.env를 열어INNORIX_API_KEY,SOURCE_ID·TARGET_ID, 감시 폴더(SOURCE_PATH)와 반영 폴더(TARGET_PATH)를 채웁니다 - 아래 명령으로 실행합니다
- 출력된
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
export INNORIX_API_KEY=your-api-key
export SOURCE_ID=device-source-01
export SOURCE_PATH=D:/hotfolder # folder to watch
export TARGET_ID=device-target-01
export TARGET_PATH=E:/mirror
export SYNC_DIRECTION=one_way # one_way | two_way
export SYNC_WATCH=file_created # file_created | file_modified
Windows PowerShell (CMD 에서는 set INNORIX_API_KEY=your-api-key 형식)
$env:INNORIX_API_KEY="your-api-key"
$env:SOURCE_ID="device-source-01"
$env:SOURCE_PATH="D:/hotfolder"
$env:TARGET_ID="device-target-01"
$env:TARGET_PATH="E:/mirror"
$env:SYNC_DIRECTION="one_way"
$env:SYNC_WATCH="file_created"
동기화 만들기
동기화 설정
동기화 동작은 transferOptions 의 두 값으로 정합니다.
방향 — syncType
| 값 | 동작 |
|---|---|
1 | 단방향(One-Way) — Source → Target |
2 | 양방향(Two-Way) — 양쪽 변경을 서로 반영 |
감시 대상 — watchFolderType
| 값 | 동작 |
|---|---|
1 | 새로 생긴 파일만 |
2 | 수정된 파일만 |
3 | 생성·수정 모두 — 일부 서버 버전에서만 지원 |
ℹ️ 연동 대상 서버가
3을 지원하지 않으면 요청이 거부됩니다. 지원 여부가 확인되지 않았다면1또는2로 동기화를 두 개 만드는 방식을 쓰세요.
동기화 만들기
{
"name": "hot-folder-sync",
"flowName": "hot-folder-sync",
"transferType": "sync",
"timezone": "Asia/Seoul",
"details": [
{
"senderId": "<sourceDeviceId>",
"receiverId": "<targetDeviceId>",
"sourceItem": [{ "filePath": "D:/hotfolder", "isDir": true }],
"targetPath": "E:/mirror",
"step": 1,
"transferOptions": {
"noSchedule": false,
"target-action": "overwrite",
"syncType": 1,
"watchFolderType": 1,
"checkIntegrity": true
}
}
],
"schedules": [
{ "type": "none", "startDateType": "now", "startDate": "2026-09-14T02:00:00.000Z", "timezone": "Asia/Seoul" }
],
"step": 1,
"isUpcoming": false
}
syncType·watchFolderType은 위 동기화 설정의 값입니다.transferOptions.target-action은 이름이 겹칠 때의 동작입니다.overwrite(덮어쓰기) ·numbering(이름 뒤에 번호) ·nosend(건너뛰기) 중 하나를 넣습니다.startDate는 예시 값입니다. 요청 시점의 현재 UTC 시각을 넣으세요 (아래 예제 코드는 실행할 때마다 현재 시각을 계산합니다).
schedules 에는 위에서 설명한 대로 now 기본값을 그대로 넣습니다.
# 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).
TZ = os.getenv("SCHEDULE_TZ", "Asia/Seoul")
SOURCE_ID = os.environ["SOURCE_ID"]
SOURCE_PATH = os.getenv("SOURCE_PATH", "D:/hotfolder") # folder to watch
TARGET_ID = os.environ["TARGET_ID"]
TARGET_PATH = os.getenv("TARGET_PATH", "E:/mirror")
SYNC_TYPE = {"one_way": 1, "two_way": 2}
WATCH = {"file_created": 1, "file_modified": 2, "both": 3} # both(3) is supported on some server versions only
SYNC_DIRECTION = os.getenv("SYNC_DIRECTION", "one_way")
SYNC_WATCH = os.getenv("SYNC_WATCH", "file_created")
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": "hot-folder-sync",
"flowName": "hot-folder-sync",
"transferType": "sync", # "sync", not "normal"
"timezone": TZ,
"details": [{
"senderId": SOURCE_ID,
"receiverId": TARGET_ID,
"sourceItem": [{"filePath": SOURCE_PATH, "isDir": True}], # watched folder (always a folder)
"targetPath": TARGET_PATH,
"step": 1,
"transferOptions": {
"noSchedule": False,
"target-action": "overwrite",
"syncType": SYNC_TYPE[SYNC_DIRECTION], # 1=One-Way, 2=Two-Way
"watchFolderType": WATCH[SYNC_WATCH], # 1=created, 2=modified, 3=both
"checkIntegrity": True,
},
}],
# Required by the request format; folder watching is what triggers a run.
"schedules": [{"type": "none", "startDateType": "now",
"startDate": now_iso(), "timezone": TZ}],
"step": 1,
"isUpcoming": False,
}
automation_id = call("POST", "/api/automations", body)["automationId"]
print(f"sync automation created: {automation_id} (one-way, on file create)")동작 확인
Sync 는 파일이 생길 때마다 전송이 발생하므로, 조회 시점에 진행 중인 전송이 없을 수 있습니다. 감시 폴더에 파일을 하나 넣고 목록을 조회하면 전송 행이 나타납니다.
GET /api/transfers?automationId=<automationId> -> rows in data.data[] whose type is not automation|history|flow
GET /api/transfers/<monitorId> → status, percent, isTerminal
동기화 중인 전송의 상태 코드는 12(syncing) 로 표시됩니다. 2(complete) 로 끝나면 그 파일 하나의 반영이 끝났다는 뜻이고, 자동화 자체는 계속 감시합니다.
SKIP_ROW_TYPES = {"automation", "history", "flow"}
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 watch(automation_id, seconds=60, interval=5):
"""Drop a file into the watched folder, then follow the transfers it triggers."""
deadline, seen = time.time() + seconds, set()
while time.time() < deadline:
result = call("GET", "/api/transfers", params={"automationId": automation_id})
records = result.get("data") if isinstance(result, dict) else result
for r in records or []:
if r.get("type") in SKIP_ROW_TYPES:
continue
mid = r.get("monitorId") or r.get("id")
detail = call("GET", f"/api/transfers/{mid}") or {}
status = detail.get("status")
key = (mid, status)
if key not in seen:
seen.add(key)
print(f" {mid}: {STATUS.get(status, status)} ({detail.get('percent', 0)}%)")
time.sleep(interval)
watch(automation_id)전송 옵션
파일 옵션
Sync 에서도 details[].transferOptions 의 파일 옵션과 processors[] 는 그대로 쓸 수 있습니다.
| 항목 | 키 | 비고 |
|---|---|---|
| 확장자 필터 | send-fileoption.extension | 특정 확장자만 동기화할 때 유용 |
| 크기 필터 | send-fileoption.fileSize | 임시 파일 제외에 활용 |
| 이름 제외 | send-fileoption.fileName | .tmp, ~$ 같은 작업 중 파일 제외 |
| 중복 처리 | target-action | 동기화는 overwrite 가 일반적 |
| 무결성 검증 | checkIntegrity | 파일마다 검증 |
파일 옵션의 값 형식은 다음과 같습니다.
{
"noSchedule": false,
"target-action": "overwrite",
"syncType": 1,
"watchFolderType": 1,
"checkIntegrity": true,
"send-fileoption": {
"extension": { "extension": ["pdf", "xlsx"], "allow": true },
"fileSize": { "size": 1048576, "over": true, "equal": true },
"fileName": { "name": "tmp", "allow": false }
}
}
target-action 은 이름이 겹칠 때의 동작입니다 — overwrite(덮어쓰기) · numbering(이름 뒤에 번호) · nosend(건너뛰기).
ℹ️ 감시 폴더에 작업 중인 임시 파일이 많다면 이름·확장자 필터를 먼저 걸어 두세요. 필터가 없으면 저장 도중인 파일까지 전송 대상이 될 수 있습니다.
전송 후 동작
전송이 끝난 뒤의 동작은 두 갈래로 나뉩니다.
① 자동화에 붙는 프로세서 — 바디의 processors[]
{
"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) 은 전송이 아니라 워크스페이스에 등록됩니다.
{
"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 매핑
| 빌더 UI | .env | API |
|---|---|---|
| 탭 = Sync | TRANSFER_TYPE=sync | transferType: "sync" |
| From 디바이스 | SOURCE_ID | details[].senderId |
| 감시 폴더 | SOURCE_PATH | details[].sourceItem[].filePath (isDir: true) |
| To 디바이스 | TARGET_ID | details[].receiverId |
| To 경로 | TARGET_PATH | details[].targetPath |
| One-Way / Two-Way | SYNC_DIRECTION=one_way|two_way | transferOptions.syncType = 1 / 2 |
| File Created / Modified / Both | SYNC_WATCH=file_created|file_modified|both | transferOptions.watchFolderType = 1 / 2 / 3 |
| (Start 없음) | START_WHEN 무시됨 | schedules 는 기본값 |
자주 겪는 오류
| 증상 | 원인과 해결 |
|---|---|
| 파일을 넣어도 전송이 안 생김 | isDir 이 false 이면 감시가 동작하지 않습니다. 감시 대상은 항상 폴더입니다. |
watchFolderType: 3 거부 | Both 는 서버 지원이 필요합니다. 1(생성) 또는 2(수정)로 나눠 만드세요. |
| Two-Way 인데 한쪽만 반영 | 양쪽 에이전트가 모두 온라인이어야 합니다. 대상 디바이스 연결 상태를 확인하세요. |
| 임시 파일까지 전송됨 | send-fileoption.fileName 으로 tmp 등을 제외하거나 확장자 허용 목록을 지정하세요. |
| 스케줄을 바꿔도 동작이 같음 | Sync 는 스케줄로 실행되지 않습니다. schedules 값은 무시됩니다. |
전송 상태가 계속 12 | 12(syncing) 는 정상 동작 상태입니다. 파일 단위로 2(complete) 가 찍힙니다. |