빠른 시작
이 가이드는 약 15분이 걸립니다. 먼저 AI 직원에게 첫 Turn을 보내고, 이어서 워크플로 Run과 웹훅까지 한 번씩 해 봅니다.
준비물
- 조직 OWNER 권한이 있는 UpServe 계정입니다. Project 생성, 키 발급, 웹훅 등록은 OWNER만 할 수 있습니다.
- Private Beta에 허용된 Project입니다. 허용 전에는 모든
/v1경로가 404를 돌려줍니다. 참여를 원하면 UpServe 팀에 문의하세요. - 호출할 Deployment 하나 이상. 조직의 AI 직원을 발행해서 만듭니다.
- 해당 Deployment를 허용한 API 키입니다. 키는 생성할 때 한 번만 보입니다.
Project 만들기, Deployment 발행, 키 발급, 웹훅 등록은 웹 앱의 설정 > 개발자(https://upserve.app/settings/developer) 화면에서 합니다. 이 화면은 로그인한 소유자의 권한으로 동작하는 관리 화면이고, API 키로는 관리 작업을 할 수 없습니다.
키는 환경변수로만 다룹니다. 코드, 로그, 저장소에 남기지 마세요.
1단계: 첫 Turn 호출하기
호출 흐름은 네 단계입니다.
GET /v1/deployments로 이 키가 부를 수 있는 Deployment를 확인합니다.POST /v1/deployments/{deployment_id}/sessions로 Session과 첫 Turn을 만듭니다. 서버는 접수를 저장한 뒤202로 응답합니다.- 같은 요청을 같은
Idempotency-Key로 다시 보내 봅니다. 새로 실행하지 않고 같은 접수를 돌려줍니다. GET /v1/sessions/{session_id}/turns/{turn_id}를 폴링해 종결 상태를 기다립니다.
curl
export UPSERVE_API_KEY="up_..."
export BASE="https://api.upserve.app"
# 1. List the Deployments this key may call
curl -s "$BASE/v1/deployments" \
-H "Authorization: Bearer $UPSERVE_API_KEY"
# 2. Create a Session and its first Turn (202 Accepted)
curl -s -X POST "$BASE/v1/deployments/$DEPLOYMENT_ID/sessions" \
-H "Authorization: Bearer $UPSERVE_API_KEY" \
-H "Idempotency-Key: demo-0001" \
-H "Content-Type: application/json" \
-d '{"input":[{"type":"TEXT","text":"Summarize this month sales."}]}'
# 3. Send the exact same request again: the same receipt comes back
# 4. Poll the Turn until it reaches a terminal status
curl -s "$BASE/v1/sessions/$SESSION_ID/turns/$TURN_ID" \
-H "Authorization: Bearer $UPSERVE_API_KEY"202 응답(receipt)은 아래 모양입니다.
{
"session_id": "6f1c0a52-0000-4000-8000-000000000001",
"turn_id": "6f1c0a52-0000-4000-8000-000000000002",
"status": "QUEUED",
"idempotency_expires_at": "2026-10-06T09:00:00+00:00",
"status_url": "/v1/sessions/6f1c0a52-0000-4000-8000-000000000001/turns/6f1c0a52-0000-4000-8000-000000000002",
"events_url": "/v1/sessions/6f1c0a52-0000-4000-8000-000000000001/events"
}Idempotency-Key는 생성, 입력, 취소, 업로드 요청에 필수입니다. 통신 실패로 응답을 못 받았다면 같은 키로 같은 본문을 다시 보내세요. 이미 접수된 요청이면 같은 receipt가 돌아오고 작업이 두 번 실행되지 않습니다. 다른 본문을 같은 키로 보내면 409 IDEMPOTENCY_CONFLICT입니다. 완료한 일을 새로 시키려면 새 키를 쓰세요.
Python
import os
import time
import uuid
import httpx
BASE = "https://api.upserve.app"
AUTH = {"Authorization": "Bearer " + os.environ["UPSERVE_API_KEY"]}
DONE = {"SUCCEEDED", "FAILED", "CANCELED", "EXPIRED", "REQUIRES_ACTION"}
def create_session(deployment_id: str, text: str, key: str) -> dict:
response = httpx.post(
f"{BASE}/v1/deployments/{deployment_id}/sessions",
headers={**AUTH, "Idempotency-Key": key},
json={"input": [{"type": "TEXT", "text": text}]},
timeout=30,
)
response.raise_for_status()
return response.json()
def wait_turn(session_id: str, turn_id: str) -> dict:
while True:
response = httpx.get(f"{BASE}/v1/sessions/{session_id}/turns/{turn_id}", headers=AUTH, timeout=30)
response.raise_for_status()
turn = response.json()
if turn["status"] in DONE:
return turn
time.sleep(2)
# Reuse the same key when you resend after a network failure
key = str(uuid.uuid4())
receipt = create_session(os.environ["DEPLOYMENT_ID"], "Summarize this month sales.", key)
turn = wait_turn(receipt["session_id"], receipt["turn_id"])
print(turn["status"], turn["usage"])
for output in turn["outputs"]:
if output["type"] == "TEXT":
print(output["text"])TypeScript
Node 22 이상의 내장 fetch를 씁니다.
const BASE = 'https://api.upserve.app';
const AUTH = { Authorization: `Bearer ${process.env.UPSERVE_API_KEY}` };
const DONE = new Set(['SUCCEEDED', 'FAILED', 'CANCELED', 'EXPIRED', 'REQUIRES_ACTION']);
async function createSession(deploymentId: string, text: string, key: string) {
const response = await fetch(`${BASE}/v1/deployments/${deploymentId}/sessions`, {
method: 'POST',
headers: { ...AUTH, 'Idempotency-Key': key, 'Content-Type': 'application/json' },
body: JSON.stringify({ input: [{ type: 'TEXT', text }] }),
});
if (!response.ok) throw new Error(JSON.stringify(await response.json()));
return response.json();
}
async function waitTurn(sessionId: string, turnId: string) {
for (;;) {
const response = await fetch(`${BASE}/v1/sessions/${sessionId}/turns/${turnId}`, { headers: AUTH });
if (!response.ok) throw new Error(JSON.stringify(await response.json()));
const turn = await response.json();
if (DONE.has(turn.status)) return turn;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
// Reuse the same key when you resend after a network failure
const receipt = await createSession(process.env.DEPLOYMENT_ID!, 'Summarize this month sales.', crypto.randomUUID());
const turn = await waitTurn(receipt.session_id, receipt.turn_id);
console.log(turn.status, turn.usage);성공한 Turn의 응답은 아래처럼 outputs에 결과가 담깁니다. usage.status가 PENDING이면 비용이 아직 확정되지 않았다는 뜻입니다.
{
"id": "6f1c0a52-0000-4000-8000-000000000002",
"session_id": "6f1c0a52-0000-4000-8000-000000000001",
"status": "SUCCEEDED",
"usage": {"status": "FINAL", "total_su": 1.25},
"error": null,
"created_at": "2026-10-05T09:00:00+00:00",
"started_at": "2026-10-05T09:00:02+00:00",
"completed_at": "2026-10-05T09:00:20+00:00",
"outputs": [
{"id": "o1", "key": "answer", "type": "TEXT", "visibility": "FINAL",
"availability": "READY", "item_id": "i1", "text": "This month sales were up 8%."}
],
"next_cursor": null
}재시도와 백오프, SSE 재연결까지 포함한 전체 예제는 runtime_client.py, runtime_client.ts, runtime_quickstart.sh에서 받을 수 있습니다. 웹훅 수신 예제는 runtime_webhook_receiver.py, runtime_webhook_receiver.ts입니다.
2단계: 워크플로 Run 실행하기
게시된 워크플로는 Session이 아니라 Run으로 호출합니다. 흐름은 다음과 같습니다.
- (선택)
POST /v1/files로 입력 파일을 올리고file_id를 받습니다. POST /v1/workflows/{workflow_id}/runs로 게시 버전과 입력을 보냅니다.202와run_id를 받습니다.GET /v1/runs/{run_id}로 상태와 진행률을 폴링하거나 SSE로 받습니다. 종결 전에는 최종 결과가 없습니다.- 종결하면
GET /v1/runs/{run_id}/outputs로 JSON, 파일, URL 결과를 읽습니다. - 파일 결과는
POST /v1/artifacts/{artifact_id}/download로 60초짜리 주소를 발급받아 바로 내려받습니다.
# 1. (Optional) Upload an input file and keep the returned file_id
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"
# 2. Start a Run on a published version (202 Accepted)
curl -s -X POST "$BASE/v1/workflows/$WORKFLOW_ID/runs" \
-H "Authorization: Bearer $UPSERVE_API_KEY" \
-H "Idempotency-Key: run-0001" \
-H "Content-Type: application/json" \
-d '{"published_version":3,"input":{"count":3},"file_ids":["'"$FILE_ID"'"]}'
# 3. Poll the Run status and progress
curl -s "$BASE/v1/runs/$RUN_ID" \
-H "Authorization: Bearer $UPSERVE_API_KEY"
# 4. Read the outputs (JSON, FILE and URL items), following next_cursor if present
curl -s "$BASE/v1/runs/$RUN_ID/outputs?limit=50" \
-H "Authorization: Bearer $UPSERVE_API_KEY"
# 5. Issue a short-lived download URL for a FILE output, then fetch it right away
curl -s -X POST "$BASE/v1/artifacts/$ARTIFACT_ID/download" \
-H "Authorization: Bearer $UPSERVE_API_KEY"
curl -s -o report.pdf "$BASE$DOWNLOAD_URL"각 단계의 자세한 규칙은 Workflow와 Run에 있습니다.
3단계: 완료 알림 받기
폴링 대신 완료 시점에 알림을 받으려면 웹훅을 씁니다. 웹훅은 개발자 화면에서 HTTPS 주소로 등록하고, 등록할 때 한 번 보이는 secret을 수신 서버에 보관합니다. 수신 서버는 서명을 검증하고, event_id로 중복을 거른 뒤, 인증된 조회로 현재 상태를 다시 읽습니다. 자세한 검증 코드는 웹훅을 보세요.