HedaAI 상품 이미지 API
상품 사진에서 전문 이커머스 이미지 12장(메인 이미지 8장 + A+ 배너 4장)과 완성된 리스팅 카피를 생성합니다 — 프로그래밍 방식으로, 또는 MCP를 통해 AI 에이전트에서 바로.
인증
hedaai.com/settings/api-keys에서 API 키를 생성하세요(모든 계정에서 가능하며, 신규 계정에는 $2.00 가입 보너스 ≈ 무료 태스크 2건이 지급됩니다). 전체 키(hda_live_…)와 해당 웹훅 시크릿(whsec_…)은 생성 시 한 번만 표시되므로 안전하게 보관하세요. 키에는 이름과 지출 한도를 지정할 수 있고 언제든 폐기할 수 있습니다.
Authorization: Bearer hda_live_...
키는 해시로 저장되며 본인 계정에만 연결됩니다 — API 키로 다른 계정의 데이터를 읽는 것은 불가능합니다.
GET /v1/account가 "mode": "sandbox_watermarked"를 반환). 첫 결제가 이루어지면 이전에 생성한 모든 상품의 워터마크가 자동으로 제거됩니다.지출 한도: 각 키에는 선택 항목인 spend_cap_cents가 있습니다(기본값: 무제한). 한도에 도달하면 태스크 생성이 spend_cap_exceeded(403)로 실패합니다.
응답 구조
{ "status": 200, "code": 200, "message": "", "data": { … } }
오류 응답에는 프로그래밍 방식 매칭을 위한 error_code가 추가됩니다. 전송 오류와 태스크 실패의 구분: HTTP 4xx/5xx는 요청이 실패했다는 뜻입니다. 생성 도중 실패한 태스크는 HTTP 오류가 아니며, 폴링은 200과 함께 status: "failed" 및 error 필드를 반환하고 과금되지 않습니다.
빠른 시작
# 1. 태스크 생성
curl -X POST https://hedaai.com/api/v1/tasks \
-H "Authorization: Bearer $HEDAAI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-12345-attempt-1" \
-d '{
"product_name": "stainless steel insulated water bottle",
"images": ["https://your-cdn.example.com/bottle-front.jpg"],
"aspect_ratio": "1:1",
"category": "insulated water bottle 750ml",
"target_market": "amazon_us"
}'
# → data.task_id = "3f8a…", data.status = "queued"
# 2. 종료 상태가 될 때까지 15–30초마다 폴링
curl -H "Authorization: Bearer $HEDAAI_API_KEY" https://hedaai.com/api/v1/tasks/3f8a…
# → data.status = "succeeded", data.product_id = "9c1b…"
# 3. 완성된 상품 조회 (이미지 URL 12개 + 리스팅 카피)
curl -H "Authorization: Bearer $HEDAAI_API_KEY" https://hedaai.com/api/v1/products/9c1b…
POST/v1/tasks
이미지 생성 태스크를 생성합니다. 선택 헤더: Idempotency-Key(≤64자, 아래 참고).
| 필드 | 타입 | 필수 | 비고 |
|---|---|---|---|
product_name | string | 예 | 짧은 상품명. 강력한 그라운딩 신호이므로 구체적으로 작성하세요. |
images | string[1–8] | 예 | 사진 URL(공개 http/https, 각 ≤15MB) 및/또는 base64 / data-URI. 다각도 사진일수록 재현도가 높아집니다. |
aspect_ratio | string | 예 | 1:1, 2:3, 3:2, 3:4, 4:3, 9:16, 16:9. Amazon 메인 이미지는 1:1을 사용합니다. |
category | string | 아니요 | 구체적인 카테고리, 예: "vacuum cleaner replacement filter". 권장. |
brand_name | string | 아니요 | 브랜드 스토리 이미지와 리스팅 카피에 반영됩니다. |
key_features | string[≤10] | 아니요 | 반드시 보여줘야 할 셀링 포인트. |
specs | object | 아니요 | {material, colorway, key_components[], usage_context, use_scenarios[], dimensions[], scale}. 권장 — API에는 대화형 검토 단계가 없으므로 그라운딩은 전적으로 보낸 정보에서 나옵니다. |
target_market | string | 아니요 | amazon_us(기본값) · shopify · ebay |
copy_language | string | 아니요 | 리스팅 카피 로케일(en-US, de-DE, ja-JP, …). 타깃 마켓에 따라 기본값이 정해집니다. |
aplus_format | string | 아니요 | banner(16:9, 기본값) · aplus_3_4 · aplus_9_16 · standard_aplus/premium(Amazon 전용) |
no_human_models | bool | 아니요 | true = 모든 이미지에 인물 없음. |
supplementary_text | string ≤5000 | 아니요 | AI가 읽을 자유 형식 사양 / 매뉴얼 발췌. |
callback_url | string | 아니요 | 종료 상태를 받을 웹훅(웹훅). |
태스크 객체를 반환합니다:
{
"task_id": "3f8a…", "status": "queued", "progress": 0,
"queue_position": 2, "eta_seconds": 840, "cost_cents": 0,
"created_at": "2026-07-17T08:00:00Z",
"urls": { "get": "/api/v1/tasks/3f8a…", "cancel": "/api/v1/tasks/3f8a…/cancel" }
}
GET/v1/tasks/{task_id}
상태 라이프사이클: queued → processing → succeeded | partial_failed | failed | canceled.
- succeeded — 12장 모두 완료;
product_id설정됨;cost_cents: 100과금됨. - partial_failed — 일부 이미지 실패; 성공한 이미지가 담긴 상품은 그대로 저장됨; 과금 없음.
- failed / canceled — 과금 없음;
error에 이유가 담깁니다.
15–30초마다 폴링하세요. 대기 중에는 queue_position / eta_seconds가 채워집니다.
POST/v1/tasks/{task_id}/cancel
태스크를 취소합니다. 대기 중에는 무료이며, 이미 완료된 태스크에는 영향이 없습니다(멱등).
GET/v1/products/{product_id}
{
"product_id": "9c1b…", "name": "stainless steel insulated water bottle",
"target_market": "amazon_us", "aspect_ratio": "1:1", "watermarked": false,
"listing_copy": {
"title": "…", "bullet_points": ["…","…","…","…","…"], "description": "…",
"search_terms": "…", "meta_title": "…", "meta_description": "…", "language": "en"
},
"images": [
{ "slice_id": "M1", "section": "main", "url": "https://images.hedaai.com/…",
"url_2k": "https://images.hedaai.com/…", "width": 2048, "height": 2048, "sort_order": 1 }
],
"created_at": "2026-07-17T08:11:32Z"
}
M1–M8은 메인 이미지 8장,A1–A4는 A+ 배너입니다.url_2k는 유료 계정에서 비동기 2K 업스케일이 끝나면 나타납니다(메인 이미지만) —succeeded보다 몇 분 늦어질 수 있습니다.succeeded직후에는 이미지 한 건이 잠시 대기 상태일 수 있습니다 — 12장보다 적게 보이면 몇 초 뒤 다시 조회하세요.- URL은 영구적이며(자체 CDN에서 제공) 핫링크와 복사 모두 가능합니다.
GET/v1/account
{
"balance_cents": 1250, "paid_balance_cents": 1000, "bonus_balance_cents": 250,
"has_paid": true, "mode": "live", "price_per_task_cents": 100,
"key": { "name": "prod-integration", "key_prefix": "hda_live_ab12…cd34",
"spend_cap_cents": null, "spent_cents": 700 }
}
멱등성
POST /v1/tasks에 Idempotency-Key 헤더(≤64자)를 전달하세요. 같은 키와 같은 본문으로 재시도하면 중복 태스크를 생성(그리고 결국 과금)하는 대신 원래 태스크를 반환합니다. 같은 키에 다른 본문을 보내면 409 idempotency_conflict가 반환됩니다. 에이전트와 자동화에 적극 권장합니다 — 네트워크 재시도가 이중 과금으로부터 안전해집니다.
웹훅
태스크 생성 시 callback_url을 지정하면 종료 상태에서 POST를 받습니다. 이벤트: task.succeeded, task.partial_failed, task.failed, task.canceled.
{ "id": "evt_…", "type": "task.succeeded", "created_at": "2026-07-17T08:11:35Z",
"data": { /* 태스크 객체 — GET /v1/tasks/{id}와 동일한 형태 */ } }
서명은 Standard Webhooks 사양을 따릅니다 — 헤더는 webhook-id, webhook-timestamp, webhook-signature: v1,<base64>입니다. 키의 webhook_secret으로 검증하세요:
import hmac, hashlib, base64
def verify(secret, msg_id, timestamp, body: bytes, signature_header):
key = base64.b64decode(secret.removeprefix("whsec_"))
expected = base64.b64encode(hmac.new(key, f"{msg_id}.{timestamp}.".encode() + body,
hashlib.sha256).digest()).decode()
return any(hmac.compare_digest(expected, s.split(",", 1)[1])
for s in signature_header.split() if s.startswith("v1,"))
2xx로 빠르게 응답하세요(무거운 작업은 비동기로 처리). 전송 방식: 즉시 1회 시도 후 3s / 6s / 12s 간격으로 재시도하고, 그다음에는 실패로 표시됩니다(폴링은 계속 사용할 수 있습니다). 오래된 타임스탬프(>5 min)는 거부하고 id로 중복을 제거하세요. callback_url은 공개 http(s) 엔드포인트여야 하며, 사설/내부 주소는 거부됩니다.
요청 제한 & 동시성
| 제한 | 값 | 초과 시 |
|---|---|---|
| 키당 요청 수 | 120 / min (전체 엔드포인트) | 429 rate_limited — RateLimit-Limit / -Remaining / -Reset 헤더를 확인하세요 |
| 키당 진행 중 태스크 | 2 (queued + processing) | 429 concurrency_limit — 태스크가 끝날 때까지 기다리세요 |
| 플랫폼 API 용량 | 동적 | 429 api_capacity — 몇 분 뒤 재시도하세요 |
생성 작업은 무겁기 때문에(태스크당 13–20회의 AI 이미지 호출) 처리량은 요청 빈도가 아니라 진행 중 태스크 슬롯 수로 결정됩니다. 더 높은 동시성이 필요하신가요? 문의하기.
오류
| error_code | HTTP | 의미 |
|---|---|---|
bad_request | 400 | 잘못된 파라미터 — 메시지에 해당 필드가 정확히 표시됩니다(예: images[2]: image download failed with HTTP 403). |
unauthorized | 401 | API 키 누락/무효, 또는 폐기된 키. |
insufficient_credits | 402 | 잔액 부족; data.balance_cents가 포함됩니다. |
spend_cap_exceeded | 403 | 이 키의 지출 한도를 모두 소진했습니다. |
account_disabled / email_not_verified / email_bounced | 403 | 계정 수준 제한. |
not_found | 404 | 태스크/상품이 존재하지 않거나 다른 계정 소유입니다. |
idempotency_conflict | 409 | 동일한 Idempotency-Key, 다른 본문. |
concurrency_limit / api_capacity / rate_limited | 429 | 요청 제한을 참고하세요. |
generation_failed / internal_error | 500 | 서버 측 오류; 멱등성 키와 함께 안전하게 재시도할 수 있습니다. |
코드 예제
태스크 생성, 종료 상태까지 폴링, 완성된 상품 조회로 이어지는 전체 흐름을 원하는 프로그래밍 언어로 확인하세요.
API=https://hedaai.com/api/v1
# 1. 태스크 생성
TASK_ID=$(curl -sS -X POST "$API/tasks" \
-H "Authorization: Bearer $HEDAAI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sku-8842-v1" \
-d '{"product_name":"stainless steel insulated water bottle",
"images":["https://your-cdn.example.com/bottle.jpg"],
"aspect_ratio":"1:1"}' | jq -r .data.task_id)
# 2. 종료 상태까지 폴링
while :; do
TASK=$(curl -sS -H "Authorization: Bearer $HEDAAI_API_KEY" "$API/tasks/$TASK_ID")
STATUS=$(echo "$TASK" | jq -r .data.status)
[ "$STATUS" = "queued" ] || [ "$STATUS" = "processing" ] || break
sleep 20
done
# 3. 이미지 + 리스팅 카피 조회
if [ "$STATUS" = "succeeded" ]; then
PRODUCT_ID=$(echo "$TASK" | jq -r .data.product_id)
curl -sS -H "Authorization: Bearer $HEDAAI_API_KEY" "$API/products/$PRODUCT_ID" \
| jq -r '.data.images[].url, .data.listing_copy.title'
fi
import time, requests
API = "https://hedaai.com/api/v1"
H = {"Authorization": "Bearer hda_live_..."}
# 1. 태스크 생성
task = requests.post(f"{API}/tasks",
headers={**H, "Idempotency-Key": "sku-8842-v1"},
json={"product_name": "stainless steel insulated water bottle",
"images": ["https://your-cdn.example.com/bottle.jpg"],
"aspect_ratio": "1:1"}).json()["data"]
# 2. 종료 상태까지 폴링
while task["status"] in ("queued", "processing"):
time.sleep(20)
task = requests.get(f"{API}/tasks/{task['task_id']}", headers=H).json()["data"]
# 3. 이미지 + 리스팅 카피 조회
if task["status"] == "succeeded":
product = requests.get(f"{API}/products/{task['product_id']}", headers=H).json()["data"]
print([img["url"] for img in product["images"]])
print(product["listing_copy"]["title"])
const API = "https://hedaai.com/api/v1";
const H = { Authorization: "Bearer hda_live_...", "Content-Type": "application/json" };
// 1. 태스크 생성
let { data: task } = await (await fetch(`${API}/tasks`, {
method: "POST",
headers: { ...H, "Idempotency-Key": "sku-8842-v1" },
body: JSON.stringify({
product_name: "stainless steel insulated water bottle",
images: ["https://your-cdn.example.com/bottle.jpg"],
aspect_ratio: "1:1",
}),
})).json();
// 2. 종료 상태까지 폴링
while (["queued", "processing"].includes(task.status)) {
await new Promise(r => setTimeout(r, 20000));
({ data: task } = await (await fetch(`${API}/tasks/${task.task_id}`, { headers: H })).json());
}
// 3. 이미지 + 리스팅 카피 조회
if (task.status === "succeeded") {
const { data: product } = await (await fetch(`${API}/products/${task.product_id}`, { headers: H })).json();
console.log(product.images.map(i => i.url), product.listing_copy.title);
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const api = "https://hedaai.com/api/v1"
type envelope struct {
Data struct {
TaskID string `json:"task_id"`
Status string `json:"status"`
ProductID string `json:"product_id"`
Images []struct {
URL string `json:"url"`
} `json:"images"`
ListingCopy struct {
Title string `json:"title"`
} `json:"listing_copy"`
} `json:"data"`
}
func call(method, url string, body []byte, extra map[string]string) (envelope, error) {
var out envelope
var rdr *bytes.Reader
if body != nil {
rdr = bytes.NewReader(body)
} else {
rdr = bytes.NewReader(nil)
}
req, err := http.NewRequest(method, url, rdr)
if err != nil {
return out, err
}
req.Header.Set("Authorization", "Bearer hda_live_...")
req.Header.Set("Content-Type", "application/json")
for k, v := range extra {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return out, err
}
defer resp.Body.Close()
return out, json.NewDecoder(resp.Body).Decode(&out)
}
func main() {
// 1. 태스크 생성
payload, _ := json.Marshal(map[string]any{
"product_name": "stainless steel insulated water bottle",
"images": []string{"https://your-cdn.example.com/bottle.jpg"},
"aspect_ratio": "1:1",
})
task, err := call("POST", api+"/tasks", payload,
map[string]string{"Idempotency-Key": "sku-8842-v1"})
if err != nil {
panic(err)
}
// 2. 종료 상태까지 폴링
for task.Data.Status == "queued" || task.Data.Status == "processing" {
time.Sleep(20 * time.Second)
if task, err = call("GET", api+"/tasks/"+task.Data.TaskID, nil, nil); err != nil {
panic(err)
}
}
// 3. 이미지 + 리스팅 카피 조회
if task.Data.Status == "succeeded" {
product, err := call("GET", api+"/products/"+task.Data.ProductID, nil, nil)
if err != nil {
panic(err)
}
for _, img := range product.Data.Images {
fmt.Println(img.URL)
}
fmt.Println(product.Data.ListingCopy.Title)
}
}
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
import com.fasterxml.jackson.databind.*; // 어떤 JSON 라이브러리든 사용 가능
public class HedaAI {
static final String API = "https://hedaai.com/api/v1";
static final HttpClient CLIENT = HttpClient.newHttpClient();
static final ObjectMapper MAPPER = new ObjectMapper();
static JsonNode call(HttpRequest.Builder b) throws Exception {
HttpRequest req = b.header("Authorization", "Bearer hda_live_...")
.header("Content-Type", "application/json")
.build();
String body = CLIENT.send(req, HttpResponse.BodyHandlers.ofString()).body();
return MAPPER.readTree(body).path("data");
}
public static void main(String[] args) throws Exception {
// 1. 태스크 생성
JsonNode task = call(HttpRequest.newBuilder(URI.create(API + "/tasks"))
.header("Idempotency-Key", "sku-8842-v1")
.POST(HttpRequest.BodyPublishers.ofString("""
{"product_name":"stainless steel insulated water bottle",
"images":["https://your-cdn.example.com/bottle.jpg"],
"aspect_ratio":"1:1"}""")));
// 2. 종료 상태까지 폴링
String status = task.path("status").asText();
while (status.equals("queued") || status.equals("processing")) {
Thread.sleep(Duration.ofSeconds(20));
task = call(HttpRequest.newBuilder(
URI.create(API + "/tasks/" + task.path("task_id").asText())).GET());
status = task.path("status").asText();
}
// 3. 이미지 + 리스팅 카피 조회
if (status.equals("succeeded")) {
JsonNode product = call(HttpRequest.newBuilder(
URI.create(API + "/products/" + task.path("product_id").asText())).GET());
product.path("images").forEach(img -> System.out.println(img.path("url").asText()));
System.out.println(product.path("listing_copy").path("title").asText());
}
}
}
<?php
$api = "https://hedaai.com/api/v1";
$headers = ["Authorization: Bearer hda_live_...", "Content-Type: application/json"];
function call($method, $url, $headers, $body = null) {
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$res = json_decode(curl_exec($ch), true); curl_close($ch);
return $res["data"];
}
// 1. 태스크 생성
$task = call("POST", "$api/tasks", array_merge($headers, ["Idempotency-Key: sku-8842-v1"]), [
"product_name" => "stainless steel insulated water bottle",
"images" => ["https://your-cdn.example.com/bottle.jpg"],
"aspect_ratio" => "1:1",
]);
// 2. 종료 상태까지 폴링
while (in_array($task["status"], ["queued", "processing"])) {
sleep(20);
$task = call("GET", "$api/tasks/{$task['task_id']}", $headers);
}
// 3. 이미지 + 리스팅 카피 조회
if ($task["status"] === "succeeded") {
$product = call("GET", "$api/products/{$task['product_id']}", $headers);
foreach ($product["images"] as $img) echo $img["url"], "\n";
echo $product["listing_copy"]["title"], "\n";
}
MCP 서버 (AI 에이전트용)
HedaAI는 호스팅형 Model Context Protocol 서버를 제공하므로 에이전트가 상품 이미지를 직접 생성할 수 있습니다:
- 엔드포인트:
https://hedaai.com/api/v1/mcp(Streamable HTTP, 무상태) - 인증: 동일한
Authorization: Bearer hda_live_…헤더 - 도구:
create_product_shoot·get_task·get_product·get_balance
claude mcp add --transport http hedaai https://hedaai.com/api/v1/mcp \
--header "Authorization: Bearer $HEDAAI_API_KEY"
{
"mcpServers": {
"hedaai": {
"url": "https://hedaai.com/api/v1/mcp",
"headers": { "Authorization": "Bearer hda_live_..." }
}
}
}
생성은 비동기입니다: 에이전트가 create_product_shoot를 호출하고(선택적으로 idempotency_key 포함), 종료 상태가 될 때까지 30–60초마다 get_task를 폴링한 뒤, get_product로 이미지 URL과 리스팅 카피를 가져옵니다. OAuth가 필요한 클라이언트(예: claude.ai 커스텀 커넥터)는 아직 지원하지 않습니다 — Claude Code, Cursor 또는 에이전트 SDK처럼 API 키를 사용할 수 있는 클라이언트를 이용하세요.
요금
| 항목 | 가격 |
|---|---|
| 상품 촬영 — 이미지 12장 + 리스팅 카피 | $1.00, 성공했을 때만 과금 |
| 가입 보너스 | $2.00 (≈ 무료 태스크 2건, 첫 결제 전까지 워터마크 적용) |
| 잔액 충전 | hedaai.com에서 $10–$1000; ≥$50 +5% 보너스, ≥$100 +10% |
잔액은 만료되지 않습니다. 구독도 없습니다. 최신 가격은 언제나 GET /v1/account(price_per_task_cents)에서 확인할 수 있습니다.