Node.js · 가상 착용(Virtual try-on) API
가상 착용(Virtual try-on) API Node.js 환경
한 줄 요약
환경 변수에 PHOTTA_API_KEY를 설정하고, Bearer 키를 포함하여 https://api.photta.app/api/v1을 호출하는 간단한 fetch 헬퍼를 작성한 후, /api/v1/tryon/apparel로 POST 요청을 보내고 status가 completed가 될 때까지 3초마다 /api/v1/tryon/apparel/{id}를 폴링하세요.
- 일반적인 소요 시간: 1.5–4 min
- 크레딧: 2K에서 5 크레딧 · 4K에서 6 크레딧
- 처리 방식: 비동기, 폴링
- 제한 사항: 600 requests/min
- 인증: Bearer 키
이 페이지의 모든 수치는 다음 출처를 기반으로 합니다: Photta API 문서 · 확인 완료
RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
-H "Authorization: Bearer $PHOTTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": true,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')
# $ID is data.id from the POST response. Poll every 3 seconds until status is "completed" or "failed".
for i in $(seq 1 120); do
RESULT=$(curl -s "https://api.photta.app/api/v1/tryon/apparel/$ID" -H "Authorization: Bearer $PHOTTA_API_KEY")
STATUS=$(echo "$RESULT" | jq -r '.data.status')
[ "$STATUS" = "completed" ] && break
[ "$STATUS" = "failed" ] && echo "generation failed" && exit 1
sleep 3
done
echo "$RESULT" | jq -r '.data.output_url'const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": true,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
}),
});
const { data } = await res.json(); // data.id, data.status
async function waitFor(id) {
for (let attempt = 0; attempt < 120; attempt++) {
const res = await fetch(`https://api.photta.app/api/v1/tryon/apparel/${id}`, { headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}` } });
const { data } = await res.json();
if (data.status === "completed") return data.output_url;
if (data.status === "failed") throw new Error(data.error_message ?? "generation failed");
await new Promise((r) => setTimeout(r, 3000));
}
throw new Error("timed out after 6 minutes");
}import os, requests
res = requests.post(
"https://api.photta.app/api/v1/tryon/apparel",
headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
json={
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": True,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
},
)
data = res.json()["data"] # data["id"], data["status"]
import time
def wait_for(job_id):
for _ in range(120):
res = requests.get(f"https://api.photta.app/api/v1/tryon/apparel/{job_id}", headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"})
data = res.json()["data"]
if data["status"] == "completed":
return data["output_url"]
if data["status"] == "failed":
raise RuntimeError(data.get("error_message", "generation failed"))
time.sleep(3)
raise TimeoutError("timed out after 6 minutes")한눈에 보기
한눈에 보기
- 엔드포인트
- POST /api/v1/tryon/apparel
- 인증
- Bearer API 키
- 처리 방식
- 비동기 — 상태 엔드포인트 폴링
- 일반적인 소요 시간
- 1.5–4 min
- 크레딧
- 2K에서 5 크레딧 · 4K에서 6 크레딧
- 화면비
- 3:4 · 1:1 · 4:3 · 9:16 · 16:9
- 입력 제한
- 이미지당 최대 10 MB; JPEG, PNG, WebP
- 멱등성
- 아니요
동작 원리
5단계, 회원가입부터 첫 이미지 생성까지 약 5분 소요.
단계 1
회원가입 및 키 생성
ai.photta.app에서 계정을 생성합니다. 대시보드의 Developers 섹션으로 이동하여 Generate API key를 클릭하고 라이브 키를 복사합니다. 키는 `photta_live_`로 시작하며 뒤이어 32자리의 16진수 문자가 붙습니다.
단계 2
소스 코드가 아닌 환경 변수에 키 저장
`.env`(또는 플랫폼의 시크릿 저장소)에 `PHOTTA_API_KEY=...`를 추가하고 `process.env`를 통해 로드하세요. 키를 절대 커밋하지 마세요. 번들러가 브라우저로 키를 노출할 수 있으므로 `'use client'`가 선언된 파일로 절대 임포트하지 마세요.
node --version # 18 or later ships fetch echo "PHOTTA_API_KEY=photta_live_xxxxx" >> .env단계 3
경량 fetch 헬퍼 작성
`fetch()`를 감싸는 20줄짜리 래퍼 하나로 기본 URL, Authorization 헤더, JSON 본문, 에러 정규화를 처리할 수 있습니다. 공식 SDK가 출시되기 전까지 필요한 추상화는 이것뿐입니다.
// photta.js — one helper for every endpoint const BASE = "https://api.photta.app/api/v1"; export async function photta(path, init = {}) { const res = await fetch(BASE + path, { ...init, headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json", ...(init.headers ?? {}) } }); const body = await res.json(); if (!res.ok) throw Object.assign(new Error(body.error?.message ?? res.statusText), { status: res.status, retryAfter: res.headers.get("Retry-After"), body }); return body; }단계 4
try-on 요청 제출 및 폴링
문서에 안내된 예제 본문과 함께 `/api/v1/tryon/apparel`로 POST 요청을 보냅니다. 아래 코드에 모든 필드가 포함되어 있습니다. API는 즉시 생성 ID를 반환합니다. `status === 'completed'`가 될 때까지 3초마다 `/api/v1/tryon/apparel/{id}`를 폴링하세요.
const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", { method: "POST", headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ "product_type": "top", "product_images": [ "https://cdn.yourstore.com/products/shirt-front.jpg" ], "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg", "product_category": "t_shirt", "mannequin_id": "mnk_f3a9b1", "pose_id": "pose_c2d4e5", "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg", "lock_background": true, "resolution": "2K", "aspect_ratio": "3:4", "body_size": "M", "expression": "neutral", "user_instruction": "Keep the collar pattern crisp and centered." }), }); const { data } = await res.json(); // data.id, data.status async function waitFor(id) { for (let attempt = 0; attempt < 120; attempt++) { const res = await fetch(`https://api.photta.app/api/v1/tryon/apparel/${id}`, { headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}` } }); const { data } = await res.json(); if (data.status === "completed") return data.output_url; if (data.status === "failed") throw new Error(data.error_message ?? "generation failed"); await new Promise((r) => setTimeout(r, 3000)); } throw new Error("timed out after 6 minutes"); }단계 5
결과 저장
완료된 페이로드에는 `output_url` 및 `thumbnail_url`이 포함됩니다. 바이트 데이터를 한 번 fetch하여 자체 객체 스토리지에 저장하세요. URL은 안정적으로 유지되지만, 프로덕션 렌더링을 Photta의 CDN에 의존해서는 안 됩니다.
오류
- 400
invalid_request_error - The request is malformed or contains invalid parameters.
- 401
authentication_error - The API key is missing, invalid, or expired.
- 402
insufficient_credits_error - Your account does not have enough credits.
- 404
not_found_error - The requested resource does not exist.
- 409
conflict_error - The request conflicts with current state — e.g. your concurrent-batch cap is reached.
- 429
rate_limit_error - Too many requests. Slow down and retry.Retry-After: 30
- 500
api_error - An internal server error occurred. Retry later.
try {
await photta("/tryon/apparel", { method: "POST", body: JSON.stringify({
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": true,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
}) });
} catch (err) {
if (err.status === 402) console.error("insufficient_credits_error: top up before retrying");
else if (err.status === 429) await new Promise((r) => setTimeout(r, Number(err.retryAfter ?? 30) * 1000));
else throw err;
}제한 사항
- API 키당 분당 600회 요청 (고정 윈도우 방식이며, 제한은 모든 요금제에서 동일합니다)
- 429 응답에는 Retry-After: 30초 헤더가 포함됩니다
- 이미지당 최대 10 MB
- 지원 형식: JPEG, PNG, WebP
- 요금제별 동시 배치 수: Free 1 · Hobby 2 · Seller 3 · Brand 4 · Studio 6
- 폴링 전용 — API는 웹훅을 전송하지 않습니다.
지금 당장 클라이언트 라이브러리보다 헬퍼가 나은 이유
- Node 18 이상은 네이티브 fetch를 지원하므로 런타임 의존성이 없습니다
- 동일한 헬퍼가 Next.js 라우트 핸들러, 서버 액션, 모든 워커 내부에서 동작합니다
- API 키는 환경 변수에 보관하세요. 번들러를 통한 유출을 방지하려면 'use client' 모듈에서 헬퍼를 import하지 마세요
- 모든 큐 및 cron과 호환: BullMQ, Inngest, Trigger.dev, Vercel Cron, Cloudflare Queues
지원하지 않는 항목
- 공식 @photta/node 패키지는 아직 제공되지 않음 — 현재 유일한 방법은 REST입니다
- 완료 신호는 폴링이 유일하며, API는 웹훅을 전송하지 않습니다
- Bearer 키 방식만 지원하며, OAuth client-credentials 플로우는 없습니다
개발자가 자주 묻는 질문
공식 Photta Node.js SDK가 있나요?
아직 없습니다. 공식적으로 안내되는 방식은 Node의 네이티브 fetch를 통한 순수 REST입니다. 이 페이지의 헬퍼가 연동 인터페이스의 전부이며, 모든 기능에 동일하게 동작합니다.
어떤 Node.js 버전을 지원하나요?
네이티브 `fetch`를 지원하는 모든 버전, 즉 Node 18 이상을 지원합니다. Node 16에서는 `node-fetch`로 폴리필하거나 버전을 업그레이드해야 합니다. 나머지 코드는 순수 ECMAScript 2020(async/await, 템플릿 리터럴, 동적 import)이므로 다른 런타임 요구사항은 없습니다.
Node에서 인증은 어떻게 처리하나요?
모든 요청에 `Authorization: Bearer photta_live_xxx`를 전송하세요. 키는 환경 변수에 저장하고 `process.env`를 통해 로드하며, 브라우저에서 실행되는 파일에서는 절대 import하지 마세요.
생성 대기 시간은 어떻게 처리하나요?
생성 엔드포인트는 완전한 비동기 방식입니다. POST 요청은 `status: 'processing'`과 함께 즉시 생성 ID를 반환합니다. `status`가 `completed` 또는 `failed`로 바뀔 때까지 3–5초마다 `GET /api/v1/tryon/apparel/{id}`를 폴링하고, 멈춘 작업으로 인해 요청이 중단되지 않도록 시도 횟수를 제한하세요.
Next.js 내부에서 API를 호출할 수 있나요?
네 — 라우트 핸들러(`app/**/route.ts`), 서버 액션(`'use server'`), 또는 미들웨어에서 호출할 수 있습니다. API 키는 서버에만 보관하고, `'use client'` 파일에서는 헬퍼를 절대 import하지 마세요. Vercel의 함수 타임아웃을 초과하는 장기 실행 작업의 경우, 폴링을 큐(Inngest, Trigger.dev, BullMQ)에 위임하고 브라우저에는 생성 ID를 반환하여 자체 상태 엔드포인트를 폴링하도록 구성하세요.
API는 에러를 어떻게 전달하나요?
2xx 이외의 응답에는 `type`, `code`, `message`, 지원용 요청 ID가 포함된 `error` 객체 JSON 본문이 반환됩니다. 일반적인 사례: 잘못된 필드를 가리키는 `param`이 포함된 400 invalid_request_error, `required` 및 `available` 수량이 포함된 402 insufficient_credits, 초 단위의 `retry_after` 및 일치하는 Retry-After 헤더가 포함된 429 rate_limit_exceeded. 5xx 에러는 지수 백오프로 안전하게 재시도할 수 있습니다.
관련 페이지
Python
Python 환경의 가상 착용(Virtual try-on) API
requests 라이브러리를 사용하여 Python에서 Photta의 가상 착용(Virtual try-on) API를 호출하세요. 30줄짜리 래퍼이자 httpx를 통한 비동기 지원이 가능하며, Django, FastAPI, Celery에서 원활하게 동작합니다.
읽기cURL
cURL 기반 가상 착용(Virtual try-on) API
cURL을 사용해 셸에서 Photta의 가상 착용(Virtual try-on) API 기능을 직접 호출하세요. 제출, polling, 다운로드까지 — 모든 스니펫은 스크립트 파이프라인 구축을 위해 jq와 연동됩니다.
읽기기능
가상 착용(Virtual try-on) API 의류용
의류를 AI 마네킹에 착용시키는 REST API입니다. 비동기식 처리, 1.5~4분의 생성 시간, 2K 기준 5 credits, 4K 기준 6 credits 소요. cURL, Node.js, Python 예제가 제공됩니다.
읽기빌드
앱에 가상 try-on 기능 구축하기
Photta API를 사용해 가상 try-on 기능을 배포하기 위한 엔드투엔드 레시피: 업로드, 제출, 워커에서의 polling, 결과 렌더링. TypeScript + 서버 전용 패턴.
읽기빌드
iOS 또는 Android 앱에 가상 try-on 추가하기
Photta API를 사용하여 네이티브 iOS 또는 Android 앱에 AI 가상 try-on을 추가하는 방법: 디바이스에 키를 절대 포함하지 말고, 백엔드를 통해 프록시 처리하며, 작업 상태를 미러링하세요.
읽기빠른 시작
5분 안에 첫 번째 Photta 이미지를 생성하세요.
회원가입, API 키 생성, 첫 cURL 호출 실행, 완료될 때까지 폴링, 결과 저장까지. 엔드투엔드로 문서화된 5단계 가이드입니다.
읽기레퍼런스
Photta API 가격 정책
2K 및 4K에서 각 Photta API endpoint에 소요되는 크레딧 비용, 과금 및 환불 방식, 그리고 무료 endpoint 목록입니다. 이미지당 별도의 현금 가격은 없습니다.
읽기
이 페이지의 모든 수치는 다음 출처를 기반으로 합니다: Photta API 문서 · 확인 완료
5분 만에 API 체험하기
Photta API 키를 발급받고 위 cURL 스니펫을 붙여넣기만 하면, 커피가 다 내려지기도 전에 첫 번째 모델 착용 컷을 받아볼 수 있습니다.
- 1.5–4 min
- 2K에서 5 크레딧 · 4K에서 6 크레딧
- 비동기, 폴링
- 600 requests/min
- Bearer 키