Pipeline Run with File — 파일 업로드와 함께 파이프라인 실행
파일을 업로드하고 해당 파이프라인을 비동기로 실행합니다. 업로드된 파일은 data-connector 스토리지에 저장되고, 그 위치가 dag_run.conf로 주입되어 파이프라인의 retriever 노드가 참조합니다.
- Endpoint:
POST /open/v1/pipelines/{pipelineId}/runs/with-file - Tag:
Open API - 인증: API Key (
Authorization: Bearer {API_KEY})
응답 래퍼/에러 포맷은 공통 사항 문서를 참고하세요.
인증
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx- 인증 방식: API Key (회사 단위)
- 회사 컨텍스트는 API Key에서 자동 resolve — 별도 tenant 헤더 불필요
- 인증 실패:
401
Request
Path Parameters
| Path param | 타입 | 필수 | 설명 |
|---|---|---|---|
pipelineId | string | Y | 실행할 파이프라인(DAG) ID. 예: tenant__abc123. Pipeline Builder에서 deploy된 DAG ID. |
Headers
| Header | 타입 | 필수 | 설명 |
|---|---|---|---|
Authorization | string | Y | Bearer 토큰 형식의 API Key. Bearer {API_KEY} |
Content-Type | string | Y | multipart/form-data (boundary는 클라이언트가 자동 생성) |
Body (multipart/form-data)
| Part | 필수 | Content-Type | 설명 |
|---|---|---|---|
file | Y | application/octet-stream / text/csv / text/tab-separated-values / application/json | 업로드할 데이터 파일. 최대 200MB |
metadata | N | application/json | 선택적 메타데이터 JSON (OpenPipelineRunFileMetadataRequest) |
metadata 스키마
{
"logicalDate": "2026-05-22T00:00:00+09:00",
"note": "daily import from analytics",
"conf": {
"any_user_key": "any_value"
}
}| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
logicalDate | string (ISO-8601) | N | Airflow DAG run의 logical date. 동일 logical date로도 새 run 생성됨 (run_id는 항상 unique 발급). 미지정 시 현재 시각. |
note | string | N | DAG run에 붙는 메모. 미지정 시 "Triggered with uploaded file <원본파일명>" 자동 생성 |
conf | object | N | 사용자 정의 dag_run.conf 키. 단, uploaded_file_* 접두 키는 서버가 항상 덮어씀 |
자동 주입되는 dag_run.conf (예약 키)
서버가 항상 덮어쓰므로 metadata.conf에 같은 키를 넣어도 무시됩니다.
| 키 | 타입 | 값 |
|---|---|---|
uploaded_file_path | string | 스토리지 URI. s3://{container}/ingest/{companyId}/{uuid}.{ext} (Azure는 abfs://, Local은 file://) |
uploaded_file_format | string | parquet / csv / tsv / json (정규화된 값) |
uploaded_file_size | number | bytes |
uploaded_file_original_name | string | 클라이언트가 보낸 원본 파일명. 빈 문자열일 수 있음 |
DAG의 retriever 노드는 이 키들을 Jinja로 참조합니다:
DataRetrievalOperator(
connection_id="tenant_{companyId}_uploaded_file_default",
query="{{ dag_run.conf['uploaded_file_path'] }}",
)지원 파일 형식
| 확장자 | 정규화 코드 | 기본 MIME |
|---|---|---|
.parquet, .pq | parquet | application/octet-stream |
.csv | csv | text/csv |
.tsv | tsv | text/tab-separated-values |
.json | json | application/json |
⚠️ 파일 형식 판정은 파일명 확장자 기준입니다. 클라이언트가 보낸
Content-Type헤더는 무시됩니다. 화이트리스트 외 확장자 →400.
Response (data) — 200 OK
OpenPipelineRunResponse (공통 envelope {code, message, data, traceId} 안):
{
"code": "SUCCESS",
"message": "OK",
"data": {
"dagRunId": "manual__<uuid>",
"dagId": "tenant__abc123",
"state": "QUEUED",
"logicalDate": "2026-05-22T00:00:00+09:00",
"startDate": null,
"note": "Triggered with uploaded file sales.parquet"
},
"traceId": "00-xxxxx-xxxxx-01"
}| 필드 | 타입 | 설명 |
|---|---|---|
dagRunId | string | Airflow DAG run ID. 폴링 / 조회 시 사용 |
dagId | string | 실행한 파이프라인 ID (= request path의 pipelineId) |
state | string | null | Airflow DagRunState enum name (QUEUED / RUNNING / SUCCESS / FAILED 등). 비동기라 응답 시점엔 QUEUED/null이 일반적 |
logicalDate | string | null | DAG run의 logical date |
startDate | string | null | 시작 시각. 응답 시점엔 null일 수 있음 |
note | string | null | DAG run note |
Airflow 내부 필드 (
triggered_by,run_type,duration,endDate)는 외부 표면에서 의도적으로 제외.
Errors
| HTTP | 메시지 | 원인 |
|---|---|---|
| 400 | 업로드된 파일이 비어 있습니다. | file part 비어있음 (0 bytes) |
| 400 | 업로드 파일이 최대 허용 크기(200MB)를 초과했습니다. | size cap 초과 |
| 400 | 지원하지 않는 파일 형식입니다. 허용: parquet(.pq)/csv/tsv/json. 받은 파일명: foo.xlsx | 확장자 화이트리스트 외 |
| 401 | API Key required / Invalid API Key | 인증 실패 |
| 404 | (Airflow) | pipelineId가 회사 소유 DAG가 아니거나 미배포 |
| 500 | (Airflow upstream) | Airflow 또는 storage 백엔드 장애 |
비동기 모델 / 상태 폴링
이 엔드포인트는 DAG run을 트리거만 하고 즉시 반환합니다. 실제 실행 완료까지 기다리지 않습니다.
응답의 dagRunId로 별도 상태 조회가 필요한 경우, 현재는 keycloak 인증 측 엔드포인트(GET /pipelines/{pipelineId}/runs/{runId})를 사용해야 합니다. Open API 측 status 폴링은 후속 PR 예정.
Examples
curl — 기본 (파일만)
curl -X POST "${HOST}/open/v1/pipelines/${PIPELINE_ID}/runs/with-file" \
-H "Authorization: Bearer ${API_KEY}" \
-F "file=@./sales.parquet"curl — metadata 포함
curl -X POST "${HOST}/open/v1/pipelines/${PIPELINE_ID}/runs/with-file" \
-H "Authorization: Bearer ${API_KEY}" \
-F "file=@./sales.parquet" \
-F 'metadata={"note":"daily","logicalDate":"2026-05-22T00:00:00+09:00","conf":{"source":"ga4"}};type=application/json'JavaScript / fetch
const fd = new FormData();
fd.append("file", fileBlob, "sales.parquet");
// metadata는 별도 Blob으로 감싸야 Content-Type을 application/json으로 지정 가능
fd.append(
"metadata",
new Blob(
[JSON.stringify({ note: "daily", conf: { source: "ga4" } })],
{ type: "application/json" }
)
);
const res = await fetch(
`${HOST}/open/v1/pipelines/${pipelineId}/runs/with-file`,
{
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
// Content-Type 절대 명시 금지 — fetch가 multipart boundary 자동 생성
},
body: fd,
}
);
const json = await res.json();
console.log(json.data.dagRunId); // "manual__<uuid>"Python / requests
import requests, json
with open("sales.parquet", "rb") as f:
res = requests.post(
f"{HOST}/open/v1/pipelines/{pipeline_id}/runs/with-file",
headers={"Authorization": f"Bearer {API_KEY}"},
files={
"file": ("sales.parquet", f, "application/octet-stream"),
"metadata": (
None,
json.dumps({"note": "daily"}),
"application/json",
),
},
)
print(res.json()["data"]["dagRunId"])사전 준비 (회사당 1회)
이 엔드포인트가 동작하려면 Airflow Connection이 사전에 시드되어 있어야 합니다.
conn_id:tenant_{companyId}_uploaded_file_defaultconn_type:uploaded_file- 자격증명 필드: 비워둠 (실제 다운로드는 worker IAM/fsspec으로 처리)
후속 PR에서
DataConnectorService.ensureUploadedFileConnection(companyId)로 자동 시드 예정. 그 전까진 운영자가 Airflow UI에서 회사별로 1회 생성.
동작 흐름 (참고)
[Client]
│ multipart: file + metadata
▼
[app-api]
│ · 검증 (size, 확장자, 비어있음)
│ · MultipartFile → temp file (메모리 적재 회피)
│ · StorageService.streamUpload() → ingest/{companyId}/{uuid}.{ext}
│ · DataConnectorService.triggerCompanyDagRun(pipelineId, conf={uploaded_file_*})
▼ (Airflow REST API v2, 회사별 Bearer 토큰)
[Airflow DAG (Pipeline Builder 생성)]
│ · DataRetrievalOperator(conn_id=..., query="{{ dag_run.conf['uploaded_file_path'] }}")
│ · 런타임에 UploadedFileConnector가 fsspec으로 스토리지에서 다운로드 + 파싱
▼
[downstream transform/load operators — 기존 그대로]제한 / 주의사항
- 최대 파일 크기: 200MB (
pipeline.ingest.max-file-size-mb설정값) - 중복 실행 방지 없음: 같은
pipelineId로 연속 호출 시 각각 별개 DAG run 생성. 기존/pipelines/{id}/run엔드포인트의 “이미 실행 중” 차단은 이 엔드포인트엔 적용되지 않음 - 파일 보존: 업로드 파일은 스토리지에 영구 저장됨 (별도 GC 없음). 운영 측에서
ingest/prefix에 S3 lifecycle 규칙(N일 만료) 적용 권장 Content-Type헤더 직접 명시 금지 (fetch/axios에서): multipart boundary가 자동 생성되도록 라이브러리에 맡길 것