광고 자리 모집 중 · 제휴 문의

Dezgo REST API를 웹 애플리케이션에 연동하기: 종량제 텍스트-이미지 생성 및 인페인팅 아키텍처 가이드

365Finds Editorial
Wiki 문서 11,430편 수록

내 AI에서 365 Wiki 사용하기

아래 프롬프트를 복사해 평소 사용하는 AI에 붙여넣고 바로 질문하세요.

전체 Skill 다운로드

먼저 프롬프트로 체험해 보세요. 365 Wiki를 자주 쓴다면 전체 365Wiki Skill을 설치해 매번 붙여넣는 수고를 줄일 수 있습니다. 평소 쓰는 AI가 없다면 Gemini나 Microsoft Copilot 웹 버전도 사용할 수 있습니다.

현대 웹 애플리케이션에 AI 이미지 생성 및 부분 재합성(인페인팅) 기능을 도입하려는 1인 풀스택 개발자나 소규모 엔지니어링 팀은 심각한 인프라 트레이드오프에 부딪히게 됩니다. 오픈소스 확산 모델(Stable Diffusion 등)을 AWS EC2 G5나 RunPod 같은 자체 관리형 GPU 인스턴스에서 호스팅하면 CUDA 드라이버 유지보수, vLLM/Diffusers 버전 관리, 콜드 스타트 문제와 더불어 유휴 시간에도 월 150~400달러 이상의 고정 인프라 비용이 지속적으로 발생합니다. 반대로 Midjourney나 Leonardo 같은 상용 SaaS 플랫폼은 월 단위 고정 구독료를 요구하며, 사용하지 않은 크레딧은 이월되지 않고 소멸되는 등 트래픽 변동성이 큰 웹 서비스에 유연하게 대응하기 어렵습니다.

Dezgo는 투명한 종량제(Pay-as-you-go) 기반 서버리스 REST API를 제공하여 이러한 기술적 딜레마를 효과적으로 해결합니다. 최소 10달러의 선불 충전금(크레딧 만료 기한 없음)으로 시작할 수 있으며, 표준 512x512 해상도의 Stable Diffusion 이미지 생성 1회당 약 0.0019달러라는 매우 낮은 단가로 픽셀 해상도와 샘플링 단계 수에 비례하여 정확한 마이크로 결제가 이루어집니다.

본 가이드에서는 풀스택 및 백엔드 엔지니어를 위해 Dezgo 공식 REST API 연동 규격, X-Dezgo-Key 헤더 인증 체계, /text2image 및 /inpainting_sdxl 엔드포인트 호출 구현, HTTP 응답 헤더 기반의 실시간 잔액 추적, 그리고 비동기 렌더링 큐 설계 패턴을 종합적으로 설명합니다.


아키텍처 비교: 자체 구축 GPU vs. 상용 SaaS vs. Dezgo REST API

본격적인 코드 구현에 앞서 세 가지 인프라 접근 방식의 특징을 비교해 봅니다.

평가 기준자체 호스팅 GPU 클러스터 (EC2 / RunPod)상용 폐쇄형 SaaS (Midjourney 등)Dezgo REST API
최소 고정 비용높음 (월 150~500달러 이상 유휴 인프라 비용)중간~높음 (사용자당 월 10~60달러 구독료)0원 (10달러 1회성 선불 충전, 월 구독료 없음)
단위 생성 비용변동폭 큼; 트래픽 저조 시 단가 급증고정 패키지; 미사용 크레딧 월말 소멸512px SD 기준 회당 ~$0.0019; 스텝 수 비례
유지보수 부담극도로 높음 (CUDA 드라이버, 메모리 누수, 스케일링)매우 낮음 (완전 관리형 블랙박스 서비스)매우 낮음 (표준 HTTP REST 호출; GPU 운영 불필요)
모델 유연성무제한 (모든 HuggingFace 가중치 및 LoRA 적용 가능)낮음 (제공사의 독점 폐쇄형 모델만 사용)높음 (SD 1.5, SD 2.1, SDXL, Lightning, LoRA 지원)
응답 데이터 규격커스텀 API 및 직렬화 파이프라인 직접 구현Discord 봇 또는 비표준 폴링 메커니즘직관적인 바이너리 스트림 (image/png) 직접 반환

Dezgo의 주요 특징은 이미지 생성 요청에 대해 처리된 PNG 바이나리 데이터를 HTTP 응답 본문으로 즉시 스트리밍하고, 소모 비용과 계정 잔액 정보를 응답 헤더를 통해 실시간 전달한다는 점입니다.


인증 메커니즘 및 보안 아키텍처

Dezgo API에 대한 모든 요청은 HTTP 요청 헤더에 X-Dezgo-Key 값을 포함하여 인증을 수행합니다. API 키는 dezgo.com/account 계정 대시보드에서 발급받을 수 있습니다.

핵심 보안 규칙: 브라우저 클라이언트에서 직접 Dezgo를 호출하지 말 것

Dezgo API 키는 개발자의 선불 현금 잔고와 직결되어 있으며, 일반 사용자를 위한 하위 권한 샌드박스를 제공하지 않습니다. 만약 브라우저 네트워크 요청이나 클라이언트 자바스크립트 코드에 X-Dezgo-Key를 노출할 경우 악의적인 사용자가 키를 탈취하여 단 몇 분 만에 충전 잔액을 모두 소진시킬 수 있습니다.

따라서 안전한 웹 아키텍처는 반드시 신뢰할 수 있는 백엔드 서버나 API 프록시 계층을 경유해야 합니다.

[브라우저 클라이언트]
       │
       │ 1. POST /api/generate (사용자 세션 / JWT / 자체 토큰 차감)
       ▼
[자체 백엔드 서버]
       │
       │ 2. 입력 검증, 유저 권한 확인
       │ 3. 비공개 "X-Dezgo-Key" 헤더 주입
       │ 4. POST https://api.dezgo.com/text2image
       ▼
[Dezgo REST 렌더링 서버]
       │
       │ 5. PNG 바이너리 스트림 + 비용 헤더 동기 반환
       ▼
[자체 백엔드 서버]
       │
       │ 6. x-dezgo-job-amount-usd, x-dezgo-balance-total-usd 헤더 파싱
       │ 7. 객체 스토리지(S3 / R2) 업로드 또는 클라이언트로 스트리밍
       ▼
[브라우저 클라이언트]

1단계: Text-to-Image 엔드포인트 연동 (/text2image)

Dezgo 텍스트-이미지 생성 엔드포인트는 multipart/form-data 또는 application/json(base64 이미지 포함) 형식의 요청을 지원하며, 렌더링된 바이너리 이미지 데이터(image/png 또는 image/jpeg)를 직접 반환합니다.

주요 요청 파라미터

Node.js / TypeScript 백엔드 구현 예제

// server/services/dezgoService.ts
import { Buffer } from "node:buffer";

interface TextToImageParams {
  prompt: string;
  negativePrompt?: string;
  model?: string;
  guidance?: number;
  steps?: number;
  width?: number;
  height?: number;
}

interface GenerationResult {
  imageBuffer: Buffer;
  contentType: string;
  jobCostUsd: number;
  remainingBalanceUsd: number;
}

export async function generateTextToImage(
  params: TextToImageParams
): Promise<GenerationResult> {
  const apiKey = process.env.DEZGO_API_KEY;
  if (!apiKey) {
    throw new Error("환경 변수 DEZGO_API_KEY가 설정되어 있지 않습니다.");
  }

  const formData = new FormData();
  formData.append("prompt", params.prompt);
  if (params.negativePrompt) {
    formData.append("negative_prompt", params.negativePrompt);
  }
  if (params.model) {
    formData.append("model", params.model);
  }
  if (params.guidance) {
    formData.append("guidance", params.guidance.toString());
  }
  if (params.steps) {
    formData.append("steps", params.steps.toString());
  }
  if (params.width) {
    formData.append("width", params.width.toString());
  }
  if (params.height) {
    formData.append("height", params.height.toString());
  }

  const response = await fetch("https://api.dezgo.com/text2image", {
    method: "POST",
    headers: {
      "X-Dezgo-Key": apiKey,
    },
    body: formData,
  });

  if (!response.ok) {
    const errorText = await response.text();
    handleDezgoHttpError(response.status, errorText);
  }

  // HTTP 응답 헤더에서 1회 렌더링 비용과 계정 잔액 추출
  const jobCostUsd = parseFloat(
    response.headers.get("x-dezgo-job-amount-usd") || "0"
  );
  const remainingBalanceUsd = parseFloat(
    response.headers.get("x-dezgo-balance-total-usd") || "0"
  );

  const arrayBuffer = await response.arrayBuffer();
  const imageBuffer = Buffer.from(arrayBuffer);
  const contentType = response.headers.get("content-type") || "image/png";

  return {
    imageBuffer,
    contentType,
    jobCostUsd,
    remainingBalanceUsd,
  };
}

function handleDezgoHttpError(status: number, message: string): never {
  switch (status) {
    case 400:
      throw new Error(`Dezgo 잘못된 파라미터 (400): ${message}`);
    case 401:
      throw new Error("Dezgo 인증 실패 (401): X-Dezgo-Key를 확인하세요.");
    case 402:
      throw new Error("Dezgo 잔액 부족 (402): 선불 계정 잔액이 모두 소진되었습니다.");
    case 429:
      throw new Error("Dezgo 요청 한도 초과 (429): 동시 요청이 너무 많습니다.");
    default:
      throw new Error(`Dezgo 서버 오류 (${status}): ${message}`);
  }
}

2단계: SDXL 인페인팅 엔드포인트 연동 (/inpainting_sdxl)

인페인팅(Inpainting)은 원본 이미지의 배경과 기본 구도를 온전히 유지하면서 마스크로 지정한 특정 영역만 프롬프트에 맞춰 새롭게 그려내는 기술입니다. Dezgo는 일반 SD 모델용 /inpainting과 고해상도 고정밀 렌더링을 지원하는 /inpainting_sdxl 엔드포인트를 제공합니다.

마스크 이미지 규격

인페인팅 호출 시에는 반드시 두 개의 이미지 파일이 필요합니다.

  1. init_image: 수정 대상이 되는 원본 기본 이미지(PNG 또는 JPEG).
  2. mask_image: 원본 이미지와 픽셀 해상도가 완벽하게 일치하는 흑백 마스크 이미지. 흰색 영역(#FFFFFF)은 모델이 새로 생성할 대상 영역을 나타내며, 검은색 영역(#000000)은 보존되어야 할 원본 픽셀을 의미합니다.

Python 백엔드 구현 예제 (FastAPI / Requests)

# app/services/dezgo_inpainting.py
import os
import requests
from typing import Dict, Any, Tuple

DEZGO_API_KEY = os.environ.get("DEZGO_API_KEY")
INPAINTING_SDXL_URL = "https://api.dezgo.com/inpainting_sdxl"

def execute_sdxl_inpainting(
    prompt: str,
    init_image_bytes: bytes,
    mask_image_bytes: bytes,
    negative_prompt: str = "",
    guidance: float = 7.5,
    steps: int = 30,
) -> Tuple[bytes, Dict[str, Any]]:
    """
    multipart/form-data 형식으로 Dezgo SDXL 인페인팅 작업을 요청합니다.
    """
    if not DEZGO_API_KEY:
        raise ValueError("시스템 환경 변수에 DEZGO_API_KEY가 정의되어 있지 않습니다.")

    headers = {
        "X-Dezgo-Key": DEZGO_API_KEY
    }

    files = {
        "init_image": ("init.png", init_image_bytes, "image/png"),
        "mask_image": ("mask.png", mask_image_bytes, "image/png"),
    }

    data = {
        "prompt": prompt,
        "negative_prompt": negative_prompt,
        "guidance": str(guidance),
        "steps": str(steps),
    }

    response = requests.post(
        INPAINTING_SDXL_URL,
        headers=headers,
        files=files,
        data=data,
        timeout=60
    )

    if response.status_code == 402:
        raise RuntimeError("Dezgo 선불 잔액이 소진되었습니다. 계정을 충전하세요.")
    elif response.status_code != 200:
        raise RuntimeError(f"Dezgo 인페인팅 실패 [{response.status_code}]: {response.text}")

    metadata = {
        "job_cost_usd": float(response.headers.get("x-dezgo-job-amount-usd", "0")),
        "remaining_balance_usd": float(response.headers.get("x-dezgo-balance-total-usd", "0")),
        "content_type": response.headers.get("Content-Type", "image/png"),
    }

    return response.content, metadata

3단계: 웹 앱 비동기 작업 큐 및 타임아웃 방지 설계

Dezgo API는 기본적으로 동기식 블로킹 방식으로 동작합니다. 요청을 전송하면 생성이 끝날 때까지 HTTP 연결이 유지되며(통상 2~8초 소요), Vercel 무료 티어의 10초 기본 실행 제한과 같은 서버리스 환경에서는 게이트웨이 타임아웃 에러를 유발하기 쉽습니다.

권장 아키텍처: Redis 큐 기반 백그라운드 워커와 SSE 연동

프로덕션 서비스에서는 사용자 요청 처리와 렌더링 과정을 분리하는 Redis 작업 큐(BullMQ 또는 Celery) 패턴을 도입해야 합니다.

[클라이언트 웹 앱] ── 1. POST /api/jobs (프롬프트/마스크 제출) ──► [웹 API 서버]
                                                                        │
                                                                        ▼
[클라이언트 웹 앱] ◄─ 3. 202 Accepted { jobId } 즉시 반환 ── 2. Redis 큐에 적재
       │                                                                │
       │                                                                ▼
       │ 4. SSE / WebSocket 또는 주기적 폴링으로 대기             [Redis / BullMQ]
       │                                                                │
       │                                                                ▼ 5. 작업 인출
       │                                                        [백그라운드 워커]
       │                                                                │
       │                                                                ├── 6. Dezgo 호출
       │                                                                ├── 7. S3 / R2 업로드
       │                                                                └── 8. DB 완료 갱신
       ◄── 9. 생성 완료 이벤트 및 CDN 이미지 URL 전달 ──────────────────┘

BullMQ 워커 코드 구현 예제

// workers/renderWorker.ts
import { Worker, Job } from "bullmq";
import { generateTextToImage } from "../services/dezgoService";
import { uploadToS3 } from "../services/storageService";
import { db } from "../services/db";

export const renderWorker = new Worker("image-generation-queue", async (job: Job) => {
  const { prompt, userId, jobId } = job.data;

  // 1. Dezgo 서비스 호출
  const { imageBuffer, contentType, jobCostUsd, remainingBalanceUsd } =
    await generateTextToImage({ prompt, steps: 30 });

  // 2. 객체 스토리지(S3 / R2)에 영구 저장
  const cdnUrl = await uploadToS3({
    key: `renders/${userId}/${jobId}.png`,
    body: imageBuffer,
    contentType,
  });

  // 3. 데이터베이스 작업 상태 갱신 및 비용 기록
  await db.jobs.update({
    where: { id: jobId },
    data: {
      status: "COMPLETED",
      outputUrl: cdnUrl,
      costUsd: jobCostUsd,
    },
  });

  // 4. 선불 잔액이 5달러 이하로 떨어지면 슬랙/디스코드 경보 전송
  if (remainingBalanceUsd < 5.0) {
    console.warn(`[잔액 경고] Dezgo API 잔액 부족: 현재 잔액 $${remainingBalanceUsd.toFixed(2)}`);
  }

  return { cdnUrl, jobCostUsd };
});

계정 잔액 관리 및 프로덕션 안정성 확보 방안

Dezgo는 신용카드 자동 결제(Auto-recharge)를 지원하지 않으므로, 잔액이 소진되기 전에 대시보드에서 수동으로 충전해야 합니다. 따라서 응답 헤더를 기반으로 잔액을 지속적으로 모니터링하는 체계가 필수적입니다.

HTTP 응답 헤더 원격 측정 지표

정상 생성 응답 시 다음 헤더들이 제공됩니다.

렌더링 없이 실시간 잔액을 확인하는 엔드포인트

비용 지출 없이 계정 잔액만 주기적으로 확인하고자 할 때는 트랜잭션 조회 엔드포인트를 호출합니다.

curl -s -H "X-Dezgo-Key: YOUR_API_KEY" https://api.dezgo.com/account/tx/last

이 요청은 최근 결제 내역과 현재 balance 수치를 포함한 JSON 응답을 반환하므로 모니터링 크론 작업에 활용하기에 적합합니다.

주요 HTTP 예외 처리 전략

  1. HTTP 402 (Payment Required): 선불 잔액이 바닥났음을 의미합니다. 백엔드에서 즉시 개발팀 경보 채널로 알림을 발송하고, 사용자에게는 시스템 500 에러 대신 친절한 안내 메시지("시스템 점검으로 인해 작업이 순차적으로 대기 중입니다")를 반환하도록 설계합니다.
  2. HTTP 429 (Too Many Requests): 동시성 한도에 도달한 상태입니다. 워커 내에서 지터를 포함한 지수 백오프(예: 1초, 2초, 4초 대기 후 재시도)를 적용합니다.
  3. HTTP 400 (Bad Request): 마스크 이미지와 원본 이미지의 크기 불일치가 주된 원인입니다. 요청 전송 전 백엔드에서 이미지의 픽셀 해상도를 사전 검증합니다.

결론

Dezgo는 유휴 GPU 인프라의 막대한 유지비와 고정형 월간 SaaS 구독제의 제약에서 벗어나고자 하는 개발자에게 최적의 선택지입니다. 신뢰할 수 있는 백엔드 프록시 구성, Redis 작업 큐를 활용한 비동기 분리, 응답 헤더 기반의 자동화된 잔액 관리를 결합하면 단 1센트 미만의 저렴한 단가로 안정적인 온디맨드 AI 이미지 생성 서비스를 구축할 수 있습니다.

참고 자료