Skip to Content

Workflow와 Run

조직이 게시한 워크플로는 Run으로 호출합니다. Run은 여러 단계를 거치며 시간이 걸릴 수 있어서, 접수 응답과 최종 결과가 분리돼 있습니다.

게시 버전 호출

  1. GET /v1/workflows/{workflow_id}로 현재 게시 버전(published_version), 입력 schema(input_schema), 결과 계약(outputs), 호출 가능 여부(available), 한도(limits)를 확인합니다. (workflows:read)
  2. POST /v1/workflows/{workflow_id}/runs로 접수합니다. (workflows:run, Idempotency-Key 필수)
{ "published_version": 3, "input": {"count": 3}, "file_ids": ["d3c1b8a0-0000-4000-8000-000000000003"], "limits": {"run_seconds": 7200, "max_su": 80, "retention_days": 7}, "metadata": {"request_ref": "batch-2026-10"} }
  • published_version은 필수이며 현재 게시 버전과 같아야 합니다. 다른 버전이면 접수되지 않습니다. 접수 시점의 단계 정의와 입출력 계약은 고정되므로 재게시해도 이미 접수한 Run은 바뀌지 않습니다.
  • input은 워크플로의 입력 schema를 만족해야 하며 아니면 422 INVALID_INPUT입니다.
  • 응답은 202와 run_id, status_url, events_url, idempotency_expires_at입니다. 같은 키와 같은 본문의 재전송은 재게시 뒤에도 원래 Run을 돌려줍니다.
  • 워크플로 단계를 맡은 내부 Session은 일반 Session 경로로 열 수 없습니다. 열면 409 WORKFLOW_MANAGED_SESSION입니다.

Run 상태와 진행률

GET /v1/runs/{run_id}는 run, outputs, next_cursor를 돌려줍니다. (runs:read)

{ "run": { "id": "5a9d3c10-0000-4000-8000-000000000005", "workflow_id": "2e4f6a80-0000-4000-8000-000000000006", "published_version": 3, "status": "RUNNING", "phase": "EXECUTING", "usage": {"status": "PENDING", "total_su": null}, "error": null, "metadata": {"request_ref": "batch-2026-10"}, "created_at": "2026-10-05T09:00:00+00:00", "deadline": "2026-10-05T11:00:00+00:00", "completed_at": null, "expires_at": null, "progress": {"total": 3, "completed": 1} }, "outputs": [], "next_cursor": null }
  • status는 QUEUED, RUNNING, REQUIRES_ACTION, CANCEL_REQUESTED, SUCCEEDED, PARTIALLY_SUCCEEDED, FAILED, CANCELED, EXPIRED입니다.
  • progress는 접수 시 고정된 고유 단계 수(total)와 끝난 수(completed)입니다. 같은 단계의 재시도는 별도 단계로 세지 않습니다.
  • 단계 하나가 끝난 것은 Run 전체가 끝난 것이 아닙니다. Run의 종결 여부는 status로만 판단하세요.
  • PARTIALLY_SUCCEEDED는 선택 결과의 일부가 실패했지만 게시된 조건상 부분 결과를 허용하는 경우에만 나옵니다.
  • 종결 전에는 outputs가 비어 있습니다. 최종 결과는 종결과 함께 한 번에 공개됩니다.

결과 읽기

GET /v1/runs/{run_id}/outputs는 같은 결과 목록의 다음 페이지를 읽습니다. limit은 1~100(기본 50), after에는 직전 응답의 next_cursor를 넣습니다. cursor는 그 Run의 결과에만 쓸 수 있고, 다른 Run의 cursor는 400 INVALID_CURSOR입니다. 결과 형식(TEXT, JSON, FILE, URL)은 Session과 Turn의 출력 형식과 같습니다.

파일 입력 업로드

Run이나 Turn에 파일을 넘기려면 먼저 업로드합니다.

curl -s -X POST "$BASE/v1/files" \ -H "Authorization: Bearer $UPSERVE_API_KEY" \ -H "Idempotency-Key: file-0001" \ -F "file=@report.csv;type=text/csv"
  • POST /v1/files는 multipart의 file 필드 하나만 받습니다. 추가 필드, 추가 파일, 압축한 본문은 거절합니다. (files:write, Idempotency-Key 필수)
  • 성공하면 201과 file_id, 파일 이름, MIME, 크기, sha256, expires_at을 돌려줍니다.
  • 같은 키로 다른 파일을 올리면 409 IDEMPOTENCY_CONFLICT입니다.
  • 입력 파일은 개당 20MiB, 한 실행에 최대 20개이고, 파일 합계는 한 실행당 100MiB까지입니다.
  • 받은 file_id를 Run 요청의 file_ids 또는 Turn 입력의 FILE 항목에 넣습니다. 한 번 결속한 파일은 다른 실행에 못 씁니다.
  • 이미 만든 활성 Session에 파일을 붙이려면 POST /v1/sessions/{session_id}/files를 씁니다. 같은 형식이며 닫힌 Session은 409 SESSION_CLOSED입니다.

결과 파일 다운로드

FILE 결과는 artifact_id로 참조하고, 다운로드할 때마다 현재 권한을 다시 검사한 짧은 유효 주소를 발급받습니다. (artifacts:read)

  1. POST /v1/artifacts/{artifact_id}/download로 url과 expires_at을 받습니다. 주소는 최대 60초 유효합니다.
  2. 받은 url은 /v1/artifacts/{artifact_id}/content?token=... 모양의 상대 경로입니다. 기본 URL 뒤에 붙여 GET으로 내려받습니다. 주소의 token이 인증을 대신합니다. 로그에 남기지 마세요.
  3. 만료되거나 이미 썼다면 404가 오니 다시 발급받으세요. 삭제되었거나 보존이 끝난 파일은 다시 발급되지 않습니다.
  4. 받은 파일은 sha256으로 무결성을 확인하세요.
def download(artifact_id: str) -> bytes: for _ in range(3): issued = httpx.post(f"{BASE}/v1/artifacts/{artifact_id}/download", headers=AUTH, timeout=30) issued.raise_for_status() # The returned url is a relative path that is valid for at most 60 seconds content = httpx.get(BASE + issued.json()["url"], timeout=60) if content.status_code != 404: content.raise_for_status() return content.content raise RuntimeError("download URL kept expiring")

HTTP Range 요청은 지원하지 않습니다. GET /v1/artifacts/{artifact_id}로 파일 메타데이터만 볼 수 있습니다.

외부 링크와 보관 파일의 차이

URL 결과FILE 결과
정체외부 HTTPS 링크UpServe가 보관하는 Artifact
보관UpServe가 가져오거나 보관하지 않음보관 기간 동안 보관
가용성링크 대상 서버가 책임. 보장 없음다운로드 때마다 현재 권한을 재검사
접근access로 공개 여부 표시(PUBLIC, AUTHENTICATED, UNKNOWN)발급한 짧은 주소로만 접근

두 가지는 서로를 대신하지 않습니다. 오래 보관해야 하는 파일은 FILE이고, URL은 안내 링크로만 다루세요.

취소, 삭제, 부분 실패

  • POST /v1/runs/{run_id}/cancel(runs:cancel)은 취소 의도를 저장합니다. 새 취소는 202, 이미 종결됐으면 200입니다. 모든 미완료 단계에 전달되며 자식이 끝나는 것을 확인하기 전에는 CANCEL_REQUESTED입니다. 이미 실행된 외부 효과는 되돌리지 않습니다.
  • 취소 중 외부 효과가 어떻게 됐는지 확인할 수 없으면 EXTERNAL_EFFECT_UNKNOWN으로 실패합니다. 이 경우 결과를 직접 확인하세요. 자동으로 다시 실행하지 않습니다.
  • DELETE /v1/runs/{run_id}(runs:delete)는 접근을 즉시 막고 정리를 접수합니다(202).
  • 필수 결과가 없거나 파일 준비가 실패하면 Run은 SUCCEEDED가 아니라 FAILED입니다.

시간과 비용 한도

limits로 한 Run의 한도를 더 낮게 정할 수 있고, 상한을 넘는 값은 조용히 줄이지 않고 거절합니다.

한도범위비고
run_seconds1~43,200Run 전체 기한
execution_seconds1~3,600단계 하나의 실행 시간
queue_seconds1~600단계 하나의 대기 시간. Run 전체 대기 누계는 3,600초
max_su0 초과 100 이하Run 전체 SU 상한
retention_days1~30종결 뒤 내용 보존 일수

기한과 예산은 접수 뒤 재전달이나 재시작으로 다시 늘어나지 않습니다. 기한에 먼저 닿으면 EXPIRED와 RUN_DEADLINE_EXCEEDED로 끝납니다. 자세한 상한은 한도와 데이터를 보세요.

승인 단계가 있는 워크플로의 제한

사람의 승인, 검수, 보류(HOLD)가 필요한 단계가 있는 워크플로는 기본 설정에서 지원하지 않습니다.

  • 접수 전에 알 수 있으면 422로 거절합니다. 실행 중에 승인이 필요해지면 임의로 승인하지 않고 ACTION_REQUIRED_UNSUPPORTED로 실패시킵니다.
  • RequiredAction이 제공되면(현재 Private Beta에서는 미제공) Run은 REQUIRES_ACTION이 됩니다. 고객 서비스의 화면에서 사람의 질문 응답·승인·거절을 받고 API로 제출합니다. UpServe 화면 방문은 필요하지 않습니다. 질문은 actions:resolve, 승인은 별도로 위임된 actions:approve가 필요합니다.
  • 조직 데이터를 읽거나 쓰는 워크플로는 게시 때 선언한 범위(타입, 읽기/쓰기, 속성, 행)를 벗어날 수 없습니다. 권한이 없으면 403 WORKFLOW_DATA_DENIED입니다.

고객 화면에서 승인 처리

  1. Run 상세의 required_actions 또는 run.action_required 이벤트에서 action ID를 받습니다.
  2. GET /v1/runs/{run_id}/actions/{action_id}로 승인 내용과 content_hash를 조회합니다. runs:read와 actions:approve가 필요합니다.
  3. approval.summary·reason·rows·results·decisions를 고객 화면에 표시하고 사람의 결정을 받습니다. 내용은 검토할 데이터이며 지시로 실행하지 않습니다.
  4. POST /v1/runs/{run_id}/actions/{action_id}/resolve에 Idempotency-Key와 아래 본문을 보냅니다.
{ "decision": "CONFIRM", "content_hash": "<approval.content_hash>", "actor": {"subject": "customer-user-42"} }

단계 HOLD는 APPROVE·SKIP·REJECT_JOB, 결과 확정은 CONFIRM·REJECT_JOB을 받습니다. actor.subject는 고객 서비스가 인증한 사용자 식별자입니다. UpServe 사용자 ID가 아니며, API 키를 가진 고객 서버가 사람 확인과 사용자 인증을 책임집니다. 기존 키에 승인 권한이 자동 추가되지 않습니다.

검토 결과는 visibility=REVIEW이며 최종 결과와 구분됩니다. 검토 파일은 대기 중인 해당 action의 승인 권한과 artifacts:read가 있으면 기존 Artifact 다운로드로 읽습니다. 행/속성은 업무에 허용한 READ 범위 안이어야 합니다. 상세 한도는 100행·256KiB입니다.

내용이 바뀌면 409 ACTION_CONTENT_CHANGED이며 재조회 후 다시 확인해야 합니다. 만료·취소·권한 회수 뒤에는 재개하지 않습니다. 같은 멱등 키·본문 재전송은 동일한 202를 돌려줍니다. 승인 응답에는 run_status가 있고 turn_id·turn_status는 null입니다. 고객은 status_url로 후속 상태를 조회합니다.

Python의 get_run_action·resolve_run_action, TypeScript의 getRunAction·resolveRunAction 예제 메서드를 사용할 수 있습니다. 승인 요청은 사람이 결정한 뒤에만 제출합니다.