Python · 가상 착용(Virtual try-on) API

가상 착용(Virtual try-on) API Python

한 줄 요약

requests를 설치하고 환경 변수에서 PHOTTA_API_KEY를 로드한 뒤, Bearer 키를 전송하고 2xx 이외의 응답에 타입 정의된 PhottaError를 발생시키는 photta() 래퍼를 작성하세요. 그런 다음 /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 문서 · 확인 완료

POST/api/v1/tryon/apparel
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단계 구성, Photta 전용 의존성 제로 — requests만으로 모든 작업이 가능합니다.

  1. 단계 1

    회원가입 및 키 생성

    다른 언어와 동일한 경로: ai.photta.app → Developers → Generate API key. 키는 `photta_live_`로 시작하며 모든 Photta 기능에 동작합니다.

  2. 단계 2

    환경 변수에서 키 로드

    환경 변수에 `PHOTTA_API_KEY`를 설정하고 `os.environ`을 통해 읽어옵니다. FastAPI나 Django에서는 부팅 시 설정 모듈로 한 번 로드하세요. 소스 코드나 공개 Git 기록에 키를 직접 포함하지 마세요.

    python3 -m pip install requests
    export PHOTTA_API_KEY="photta_live_xxxxx"
  3. 단계 3

    requests 래퍼 작성

    30줄짜리 `photta_request()` 함수 하나로 기본 URL, Authorization 헤더, JSON 직렬화, 에러 변환까지 모두 처리합니다. Python SDK가 출시되기 전까지 필요한 유일한 추상화입니다.

    # photta.py — one helper for every endpoint
    import os, requests
    BASE = "https://api.photta.app/api/v1"
    
    class PhottaError(Exception):
        def __init__(self, status, error, retry_after=None):
            super().__init__(error.get("message", "request failed"))
            self.status, self.type, self.retry_after = status, error.get("type"), retry_after
    
    def photta(method, path, **kwargs):
        res = requests.request(method, BASE + path, headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"}, timeout=30, **kwargs)
        body = res.json()
        if not res.ok:
            raise PhottaError(res.status_code, body.get("error", {}), res.headers.get("Retry-After"))
        return body
  4. 단계 4

    요청 제출 및 polling

    `/api/v1/tryon/apparel`로 POST 요청을 보내 generation ID를 받은 다음, 3초 간격으로 `/api/v1/tryon/apparel/{id}`를 polling하세요. Celery나 RQ 환경에서는 polling 루프를 여러 태스크로 분산하여 몇 분씩 걸리는 작업이 워커를 점유하지 않도록 하세요.

    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")
  5. 단계 5

    결과 저장

    완료된 payload에는 `output_url`과 `thumbnail_url`이 포함됩니다. 장기 렌더링 측면에서 프로덕트가 Photta의 CDN에 종속되지 않도록 바이트 데이터를 자체 오브젝트 스토리지(S3, GCS, Cloudflare R2)에 다운로드하세요.

오류

400invalid_request_error
The request is malformed or contains invalid parameters.
401authentication_error
The API key is missing, invalid, or expired.
402insufficient_credits_error
Your account does not have enough credits.
404not_found_error
The requested resource does not exist.
409conflict_error
The request conflicts with current state — e.g. your concurrent-batch cap is reached.
429rate_limit_error
Too many requests. Slow down and retry.Retry-After: 30
500api_error
An internal server error occurred. Retry later.
try:
    photta("POST", "/tryon/apparel", 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."
    })
except PhottaError as e:
    if e.status == 402:
        print("insufficient_credits_error: top up before retrying")
    elif e.status == 429:
        time.sleep(int(e.retry_after or 30))
    else:
        raise

제한 사항

  • API 키당 분당 600회 요청 (고정 윈도우 방식이며, 제한은 모든 요금제에서 동일합니다)
  • 429 응답에는 Retry-After: 30초 헤더가 포함됩니다
  • 이미지당 최대 10 MB
  • 지원 형식: JPEG, PNG, WebP
  • 요금제별 동시 배치 수: Free 1 · Hobby 2 · Seller 3 · Brand 4 · Studio 6
  • 폴링 전용 — API는 웹훅을 전송하지 않습니다.

현재 requests가 최적의 선택인 이유

  • Photta 전용 의존성 없음 — requests는 이미 대부분의 Python 프로젝트에 포함되어 있습니다
  • Python이 구동되는 모든 환경 지원: Django, FastAPI, Flask, Celery 워커, Lambda, 스크립트
  • 클라이언트 래퍼는 약 30줄에 불과합니다 — photta.py에 붙여넣고 바로 사용하세요
  • 병렬 요청이 필요할 경우 httpx를 통해 asyncio와 결합 가능

지원하지 않는 항목

  • 아직 공식 Photta Python 패키지는 없습니다 — requests를 사용한 REST 호출이 문서화된 표준 방식입니다
  • 완료 감지는 오직 polling으로만 가능합니다. API는 웹훅을 전송하지 않습니다
  • Bearer 키 방식만 지원합니다. OAuth client-credentials 플로우는 없습니다

개발자가 자주 묻는 질문

공식 Photta Python SDK가 있나요?

아직 없습니다. 공식 지원 연동 방식은 requests 라이브러리(동기) 또는 httpx(비동기)입니다. 둘 다 간단한 래퍼 함수 하나면 충분하며, 이 페이지의 래퍼는 모든 기능에 동작합니다.

어떤 Python 버전을 지원하나요?

Python 3.9 이상을 지원합니다. 해당 래퍼는 3.10+ 환경에서 유효한 타입 힌트, f-string, `str | None` 문법을 사용합니다. 3.9에서는 `str | None`을 `Optional[str]`로 변경하세요. requests 자체는 공식적으로 3.8+를 지원하지만, 현대적인 Photta 프로젝트 기준 실질적 최소 사양은 3.9+입니다.

requests와 httpx 중 무엇을 사용해야 하나요?

순차적인 호출을 몇 번만 수행한다면 requests를 권장합니다. 더 단순하고 이미 대부분의 코드베이스에 존재하기 때문입니다. 비동기 동시성(병렬 요청, 대량 백필)이나 HTTP/2가 필요하다면 httpx를 사용하세요. 어느 쪽이든 래퍼는 매우 단순하며, `requests.request`를 `httpx.Client().request`로 바꾸기만 하면 나머지 코드는 동일하게 유지됩니다.

Django에서 호출할 수 있나요?

네. 래퍼를 `services/photta.py`에 두고 view나 Celery 태스크에서 import해 사용하세요. 작업 처리 시간이 길다면 동기 요청 핸들러 내에서 API를 직접 호출하지 마세요. view에서는 요청만 제출하고 generation ID를 반환한 뒤, 백그라운드 태스크나 Channels consumer에서 polling하세요.

Celery 워커에서 어떻게 polling하나요?

한 태스크에서 작업을 제출한 다음, 단일 polling 후 작업이 완료되거나 최대 재시도 횟수에 도달할 때까지 자신을 다시 스케줄링하는 후속 태스크를 예약(`apply_async(countdown=3)`)하세요. 이렇게 하면 워커를 유휴 상태로 유지하고 오토스케일러에 문제를 일으키는 장기 실행 태스크를 방지할 수 있습니다.

API는 에러를 어떻게 전달하나요?

2xx 이외의 응답은 `error` 객체(`type`, `code`, `message`, 400의 경우 `param`, 429의 경우 `retry_after`)가 포함된 JSON 본문을 반환합니다. 이 페이지의 래퍼는 이 모든 정보를 담은 `PhottaError`를 발생시키므로 호출부에서 본문을 직접 파싱하지 않고도 상태 코드별로 분기 처리할 수 있습니다.

관련 페이지

이 페이지의 모든 수치는 다음 출처를 기반으로 합니다: Photta API 문서 · 확인 완료

5분 만에 API 체험하기

Photta API 키를 발급받고 위 cURL 스니펫을 붙여넣기만 하면, 커피가 다 내려지기도 전에 첫 번째 모델 착용 컷을 받아볼 수 있습니다.

  • 1.5–4 min
  • 2K에서 5 크레딧 · 4K에서 6 크레딧
  • 비동기, 폴링
  • 600 requests/min
  • Bearer 키