HiAPI
블로그

튜토리얼

hiapi API로 flux-2-klein-9b/image-to-image 사용하는 방법: curl, Python 및 실행 가능한 요청

Jul 30, 2026
hiapi API로 flux-2-klein-9b/image-to-image 사용하는 방법: curl, Python 및 실행 가능한 요청

이 튜토리얼은 hiapi 통합 작업 API를 통해 flux-2-klein-9b의 image-to-image 모드를 호출하는 방법을 보여줍니다: 정확한 요청 구조, 실행 가능한 curl 명령, 백엔드에 바로 넣을 수 있는 Python 스크립트, 그리고 플랫폼의 다른 모델에서 마이그레이션해 왔다면 반드시 마주치게 될 스키마 특이점 하나까지 다룹니다.

다 읽고 나면 단일 참조 이미지 URL과 편집 프롬프트를 받아 다운로드 가능한 생성 이미지 URL을 반환하는 스크립트를 갖게 됩니다.

준비물

  • hiapi 계정과 API key. hiapi 콘솔에서 발급받을 수 있습니다.
  • 공용 HTTPS URL에 호스팅된 정확히 한 장의 참조 이미지. 플랫폼의 일부 image-to-image 모델이 여러 참조 이미지를 받는 것과 달리, flux-2-klein-9b의 image_urls 배열은 최대 한 개입니다—자세한 내용은 아래를 참고하세요.
  • 편집 또는 변환을 설명하는 텍스트 프롬프트.

모든 요청은 통합 작업 엔드포인트로 전송되며, 키는 Authorization 요청 헤더에 넣습니다:

POST https://api.hiapi.ai/v1/tasks
Authorization: Bearer sk-<your-key>
Content-Type: application/json

요청 구조 (그리고 사람들이 자주 걸리는 필드 하나)

모델 id는 flux-2-klein-9b/image-to-image입니다—/v1/models에 나열된 것과 완전히 동일하게 그대로 전달하세요. 이 모델의 input 스키마는 매우 엄격합니다:

필드 타입 필수 여부 설명
prompt string 예 편집 지시문.
image_urls string[] 예 정확히 하나의 참조 이미지 URL—minItems와 maxItems가 모두 1입니다.
aspect_ratio string 아니오 선택 가능한 값 중 하나: 1:1, 4:3, 3:4, 16:9, 9:16.
seed integer 아니오 고정하면 결과를 재현할 수 있습니다.
output_format string 아니오 선택 가능한 값 중 하나: jpeg, png, webp.

코드를 작성하기 전에 알아둘 점 두 가지:

  1. image_urls는 범위가 아니라 단 하나의 URL만 받습니다. 참조 이미지를 1–14장 받는 모델을 기준으로 공용 제출 코드를 만들어 두었다면, 여기서 URL 두 개를 보내면 거부됩니다—maxItems: got 2, want 1. 이 모델은 다중 참조 이미지 융합이 아니라 단일 이미지 편집을 수행합니다.
  2. 추가 필드는 무시되지 않고 거부됩니다. resolution, size, n, strength, image_size, num_images—플랫폼의 다른 이미지 모델에서는 흔한 이 필드들이 여기서는 모두 400 INVALID_REQUEST: additional properties '<field>' not allowed를 반환합니다. 이 모델의 스키마를 확인하지 않고 다른 모델의 payload를 재사용하지 마세요.

최소 실행 예제: curl

작업 생성:

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "flux-2-klein-9b/image-to-image",
    "input": {
      "prompt": "Change the jacket to charcoal grey leather, keep the pose and background unchanged",
      "image_urls": ["https://your-cdn.example.com/subject.png"],
      "aspect_ratio": "3:4",
      "output_format": "png"
    }
  }'

제출에 성공하면 data에 작업 id가 반환됩니다:

{"code": 200, "data": {"taskId": "tk-hiapi-..."}}

결과를 폴링:

curl https://api.hiapi.ai/v1/tasks/tk-hiapi-... \
  -H "Authorization: Bearer sk-<your-key>"

실행 중에 data.status는 queued → handling → archiving 순서로 진행됩니다. 완료되면 "success"로 바뀌고 이미지가 data.output에 나타납니다:

{
  "code": 200,
  "data": {
    "status": "success",
    "output": [{"url": "https://.../result.png", "expireAt": "..."}]
  }
}

출력 URL은 수명이 짧습니다 (expireAt에 주목). 즉시 바이트를 다운로드해 직접 저장하세요—원본 URL을 핫링크하거나 영구 저장하지 마세요.

프로덕션급 Python: 제출, 폴링, 다운로드

import time
import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
API_KEY = "sk-<your-key>"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}


def edit_image(prompt: str, image_url: str,
               aspect_ratio: str = "1:1", output_format: str = "png") -> bytes:
    # 1. 작업 생성—이 모델의 image_urls는 정확히 하나의 URL만 받습니다
    resp = requests.post(API_BASE, headers=HEADERS, json={
        "model": "flux-2-klein-9b/image-to-image",
        "input": {
            "prompt": prompt,
            "image_urls": [image_url],
            "aspect_ratio": aspect_ratio,
            "output_format": output_format,
        },
    }, timeout=60)
    body = resp.json()
    if resp.status_code != 200 or not (body.get("data") or {}).get("taskId"):
        raise RuntimeError(f"create failed [{resp.status_code}]: {body}")
    task_id = body["data"]["taskId"]

    # 2. 종료 상태가 될 때까지 폴링
    deadline = time.time() + 600
    while time.time() < deadline:
        task = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS,
                            timeout=30).json().get("data") or {}
        if task.get("status") == "success":
            outputs = task.get("output") or []
            if not outputs or not outputs[0].get("url"):
                raise RuntimeError(f"task {task_id} succeeded but returned no output URL")
            # 3. 즉시 다운로드—URL은 만료됩니다
            img = requests.get(outputs[0]["url"], timeout=120)
            img.raise_for_status()
            return img.content
        if task.get("status") == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
        time.sleep(5)
    raise TimeoutError(f"task {task_id} still running after 600s")


if __name__ == "__main__":
    png = edit_image(
        "Change the jacket to charcoal grey leather, keep the pose and background unchanged",
        "https://your-cdn.example.com/subject.png",
        aspect_ratio="3:4",
    )
    with open("edited.png", "wb") as f:
        f.write(png)
    print(f"saved edited.png ({len(png)} bytes)")

seed로 재현 가능한 편집 만들기

정수 seed를 전달하면 모델이 해당 생성에서 이를 재사용합니다. 다른 모든 것은 그대로 두고 프롬프트 문구만 A/B 테스트하고 싶을 때 유용합니다:

{
  "model": "flux-2-klein-9b/image-to-image",
  "input": {
    "prompt": "Change the jacket to charcoal grey leather, keep the pose and background unchanged",
    "image_urls": ["https://your-cdn.example.com/subject.png"],
    "seed": 42
  }
}

생략하면 모델이 호출할 때마다 seed를 무작위로 선택하며, 이는 정상적인 프로덕션 트래픽에 바람직한 동작입니다—다른 결과물이 필요할 때는 seed가 아니라 프롬프트를 바꾸세요.

폴링 대신 콜백 사용하기

스크립트와 worker 큐에는 폴링이면 충분합니다. 웹 백엔드의 경우 작업 생성 시 콜백을 등록해 hiapi가 종료 상태를 푸시하도록 하세요—callback 객체는 요청 최상위에, model과 같은 레벨에 위치합니다:

{
  "model": "flux-2-klein-9b/image-to-image",
  "input": { "...": "..." },
  "callback": {
    "url": "https://your-app.example.com/hooks/hiapi",
    "when": "final"
  }
}

"when": "final"을 사용하면 작업이 성공 또는 실패 상태에 도달했을 때 정확히 한 번 POST를 받습니다. 핸들러에는 두 가지 규칙이 있습니다:

  • 멱등하게 만드세요. 처리 로직을 작업 id 기준으로 키잉하여 재전송된 webhook이 중복 처리되지 않게 하세요. 제출 시 작업 id를 저장하고, 콜백을 유일한 진실의 원천이 아니라 “가져와서 종결하기”로 취급하세요.
  • 대사(reconciliation) 경로를 유지하세요. 콜백 발송 시점에 엔드포인트를 사용할 수 없었다면, 미해결 id를 주기적으로 스캔해 /v1/tasks/<id>를 GET 하면 누락된 것을 잡아낼 수 있습니다.

콜백이 도달하지 않는 것 같다면 hiapi 작업 콜백이 발생하지 않는 이유를 하나씩 점검하세요—발생 가능성 순서대로 일반적인 장애 패턴을 다룹니다.

오류 처리: 세 가지 핵심 실패 시나리오

생성 시 401 permission_denied—키가 이 모델을 사용할 수 없습니다. 응답 본문은 다음과 같습니다:

{"error": {"code": "permission_denied", "message": "...", "request_id": "...", "type": "hiapi_error"}}

이 문제가 발생하면 API key 무효 여부를 확인하거나 콘솔에서 키의 모델 권한을 확인하세요.

생성 시 400 INVALID_REQUEST—input이 이 모델의 스키마와 맞지 않습니다. 메시지가 위반 필드를 직접 지적합니다 (예: <root>: additional properties 'resolution' not allowed, 또는 image_urls: maxItems: got 2, want 1). 이는 영구적 오류입니다—같은 payload로 재시도하면 영원히 실패합니다; 필드를 수정한 뒤 다시 보내세요.

작업이 status: "fail" 반환—생성은 성공했지만 생성이 실패했습니다 (백엔드가 가져올 수 없는 참조 URL, 콘텐츠 정책, 업스트림 용량). 작업의 error.code와 error.message가 어느 경우인지 알려줍니다. 400과 달리 이 중 일부는 일시적이며 백오프를 두고 한 번 재시도할 가치가 있습니다.

그 외 알아둘 점: 존재하지 않거나 만료된 작업 id로 GET하면 404 task not found를 반환합니다—대사 스캔에서는 이를 “계속 대기 중”이 아니라 종료 상태로 취급해야 합니다.

다음 단계

자주 묻는 질문

flux-2-klein-9b image-to-image에 여러 참조 이미지를 전달할 수 있나요? 아니요. image_urls의 minItems와 maxItems가 모두 1로 설정되어 있습니다—정확히 하나의 URL, 그 이상도 이하도 아닙니다. 다중 참조 이미지 융합이 필요하다면 더 넓은 범위를 문서화한 모델, 예를 들어 seedream-5.0-lite를 확인하세요.

왜 “additional properties 'resolution' not allowed”가 나타나나요? 작업 API의 input 스키마는 모델별로 다릅니다. flux-2-klein-9b/image-to-image는 resolution이나 size 필드를 전혀 받지 않습니다—출력 크기는 입력 이미지와 aspect_ratio를 따릅니다. 다른 모델 payload에서 가져온 크기 필드를 모두 제거하세요.

참조 이미지를 URL 대신 base64로 보낼 수 있나요? 이 모델의 스키마는 image_urls—URL 배열을 받습니다. 이미지를 접근 가능한 HTTPS URL(모든 CDN, 오브젝트 스토리지 버킷 또는 미리 서명된 URL 가능)에 호스팅하고 그 URL을 전달하세요.

seed는 실제로 무엇을 제어하나요? 동일한 seed, 동일한 prompt, 참조 이미지를 사용하면 동일한 생성 결과가 재현되며, 이는 통제된 프롬프트 A/B 테스트에 유용합니다. 정상 트래픽에서는 생략하여 호출마다 자체적인 무작위 seed를 갖게 하세요.

같은 코드가 flux-2-klein-9b text-to-image에도 적용되나요? 엔드포인트와 흐름은 같지만 text-to-image는 자체 input 스키마를 가집니다 (image_urls 없음). 모델 id를 flux-2-klein-9b/text-to-image로 바꾸고 참조 이미지를 제거하세요—필수 필드가 모드에 따라 다르므로 테스트 호출로 검증하세요.

이미지 한 장당 비용은 얼마인가요? 가격은 생성별 사용량 기준으로 청구됩니다. 가격 페이지에서 실시간 요율을 확인하세요.

영구 AI 아티팩트로 빌드 시작하기

API 키를 받아 태스크 기반의 하나의 API로 이미지, 비디오, 오디오 모델을 호출하세요.

API 문서 보기