Makernice

Makernice API 문서: 빠른 시작

Makernice 팀 ·

적합한 용도
REST로 자체 코드에서 영상·이미지 생성을 호출하는 개발자. 견적을 받고, 생성을 요청한 뒤, 폴링하거나 웹훅으로 결과를 받습니다.
적합하지 않은 용도
Makernice API는 코드를 작성하지 않고 영상이나 이미지를 만드는 용도가 아닙니다. 그런 경우에는 콘솔의 만들기 메뉴를 사용해 주십시오. 같은 모델, 같은 가격, 같은 잔액을 씁니다.

API 키 하나와 요청 세 번으로 영상을 생성합니다. 견적을 받고, 생성을 만든 뒤, 완료될 때까지 폴링합니다.

1. API 키 만들기

콘솔에서 개발자 → API 키를 열고 키를 만드십시오. 모든 요청에 Bearer 토큰으로 보냅니다. 생성 비용은 지갑에서 차감되며, 키마다 지출 한도를 걸 수 있습니다.

2. 견적 받기

POSThttps://api.makernice.com/v1/generations/quote
curl
curl https://api.makernice.com/v1/generations/quote \
  -H "Authorization: Bearer $MAKERNICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"seedance-2.5","mode":"t2v","prompt":"온실 안을 천천히 지나가는 돌리 숏","duration":5,"resolution":"720p"}'

# → (예시 값) { "id": "qt_…", "amount": "500000", "expires_at": "…", "facts": { "quantity": 5, … } }

금액은 미국 달러 기준이며 100만분의 1달러(micro-USD) 단위입니다. 1,000,000 = $1.00입니다. 견적은 가격을 12분 동안 고정합니다.

3. 생성 만들기

POSThttps://api.makernice.com/v1/generations
curl
curl https://api.makernice.com/v1/generations \
  -H "Authorization: Bearer $MAKERNICE_API_KEY" \
  -H "Idempotency-Key: 7c1e9a2b-order-1842" \
  -H "Content-Type: application/json" \
  -d '{"quote_id":"qt_…","input":{"model":"seedance-2.5","mode":"t2v","prompt":"온실 안을 천천히 지나가는 돌리 숏","duration":5,"resolution":"720p"}}'

# → 202 { "id": "gen_…", "status": "queued", "billing": "held", … }

금액은 잔액에서 보류됩니다. 생성이 실패하거나 대기 중에 취소하면 보류가 전액 해제됩니다. 같은 Idempotency-Key로 다시 시도하면 두 번 차감되지 않고 같은 생성이 반환됩니다.

4. 완료될 때까지 폴링

GEThttps://api.makernice.com/v1/generations/{id}
curl
curl https://api.makernice.com/v1/generations/gen_… \
  -H "Authorization: Bearer $MAKERNICE_API_KEY"

# status: queued → generating → moderating → succeeded | failed | canceled
# succeeded가 되면: outputs[0].url을 키와 함께 GET 하면 파일로 302 리디렉션됩니다

5. 파일 다운로드

curl
curl -L -o video.mp4 https://api.makernice.com/v1/generations/gen_…/outputs/0 \
  -H "Authorization: Bearer $MAKERNICE_API_KEY"

Python: 견적, 생성, 폴링

MAKERNICE_API_KEY를 설정한 뒤 실행하십시오.

Python (requests)
import os
import time
import uuid
from decimal import Decimal

import requests

API = "https://api.makernice.com/v1"
ORIGIN = "https://api.makernice.com"
HEADERS = {"Authorization": f"Bearer {os.environ['MAKERNICE_API_KEY']}"}
TIMEOUT_SECONDS = 30
POLL_INTERVAL_SECONDS = 5
MAX_WAIT_SECONDS = 30 * 60
FINAL = {"succeeded", "failed", "canceled"}


def call(method, path, headers=None, **kwargs):
    # Waits out 429 (rate_limited, concurrency_limit_reached) using Retry-After; raises on any other error.
    for _ in range(5):
        r = requests.request(method, API + path, headers={**HEADERS, **(headers or {})}, timeout=TIMEOUT_SECONDS, **kwargs)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "5")))
            continue
        if not r.ok:
            raise RuntimeError(f"{method} {path} -> {r.status_code}: {r.text}")
        return r.json()
    raise RuntimeError(f"{method} {path}: still rate limited")


job = {
    "model": "seedance-2.5",
    "mode": "t2v",
    "prompt": "A slow dolly shot through a greenhouse",
    "duration": 5,
    "resolution": "720p",
}

# 1. Quote: the exact amount, locked for 12 minutes
quote = call("POST", "/generations/quote", json=job)
usd = Decimal(quote["amount"]).scaleb(-6)  # micro-USD integer string -> dollars, no float rounding
print(f"Quoted ${usd} (price list v{quote['price_version']}), valid until {quote['expires_at']}")

# 2. Create: same input + quote_id. A retry with the same Idempotency-Key returns the same generation
idempotency_key = str(uuid.uuid4())
gen = call("POST", "/generations", json={"quote_id": quote["id"], "input": job}, headers={"Idempotency-Key": idempotency_key})

# 3. Poll: queued -> generating -> moderating -> succeeded | failed | canceled (checking: keep polling)
deadline = time.monotonic() + MAX_WAIT_SECONDS
while gen["status"] not in FINAL:
    if time.monotonic() > deadline:
        raise SystemExit(f"{gen['id']} is still {gen['status']}; poll it again later")
    time.sleep(POLL_INTERVAL_SECONDS)
    gen = call("GET", f"/generations/{gen['id']}")

if gen["status"] != "succeeded":
    # billing is "released": the hold went back to your balance
    raise SystemExit(f"{gen['status']}, billing {gen['billing']}: {gen['error']}")

# 4. Download: outputs[].url is relative to the API origin and redirects (302) to the file
output = gen["outputs"][0]
if output["state"] == "blocked":
    raise SystemExit("Output blocked by moderation (the run is charged); contact support for a manual review")
r = requests.get(ORIGIN + output["url"], headers=HEADERS, timeout=120)  # requests drops Authorization on the redirect
r.raise_for_status()
with open("output.mp4", "wb") as f:
    f.write(r.content)
print(f"Saved output.mp4, charged ${Decimal(gen['amount']).scaleb(-6)}")

Node.js: 견적, 생성, 폴링

Node.js 18 이상이 필요하며 별도 패키지는 필요 없습니다. quickstart.mjs로 저장하고 MAKERNICE_API_KEY를 설정한 뒤 node quickstart.mjs를 실행하십시오.

Node.js (fetch)
// Node.js 18+ (built-in fetch, no packages). Save as quickstart.mjs and run: node quickstart.mjs
import { randomUUID } from "node:crypto";
import { writeFile } from "node:fs/promises";

const API = "https://api.makernice.com/v1";
const ORIGIN = "https://api.makernice.com";
const HEADERS = { Authorization: `Bearer ${process.env.MAKERNICE_API_KEY}` };
const TIMEOUT_MS = 30_000;
const POLL_INTERVAL_MS = 5_000;
const MAX_WAIT_MS = 30 * 60 * 1000;
const FINAL = new Set(["succeeded", "failed", "canceled"]);

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// micro-USD integer string -> dollars, no float rounding
const usd = (micro) => `${BigInt(micro) / 1_000_000n}.${(BigInt(micro) % 1_000_000n).toString().padStart(6, "0")}`;

class Stop extends Error {}

async function call(method, path, { body, headers } = {}) {
  // Waits out 429 (rate_limited, concurrency_limit_reached) using Retry-After; throws on any other error.
  for (let attempt = 0; attempt < 5; attempt++) {
    const r = await fetch(API + path, { signal: AbortSignal.timeout(TIMEOUT_MS),
      method,
      headers: { ...HEADERS, ...(body ? { "Content-Type": "application/json" } : {}), ...headers },
      body: body ? JSON.stringify(body) : undefined,
    });
    if (r.status === 429) {
      await sleep(Number(r.headers.get("Retry-After") ?? "5") * 1000);
      continue;
    }
    if (!r.ok) throw new Stop(`${method} ${path} -> ${r.status}: ${await r.text()}`);
    return r.json();
  }
  throw new Stop(`${method} ${path}: still rate limited`);
}

async function main() {
  const job = {
    "model": "seedance-2.5",
    "mode": "t2v",
    "prompt": "A slow dolly shot through a greenhouse",
    "duration": 5,
    "resolution": "720p",
  };

  // 1. Quote: the exact amount, locked for 12 minutes
  const quote = await call("POST", "/generations/quote", { body: job });
  console.log(`Quoted $${usd(quote.amount)} (price list v${quote.price_version}), valid until ${quote.expires_at}`);

  // 2. Create: same input + quote_id. A retry with the same Idempotency-Key returns the same generation
  const idempotencyKey = randomUUID();
  let gen = await call("POST", "/generations", { body: { quote_id: quote.id, input: job }, headers: { "Idempotency-Key": idempotencyKey } });

  // 3. Poll: queued -> generating -> moderating -> succeeded | failed | canceled (checking: keep polling)
  const deadline = Date.now() + MAX_WAIT_MS;
  while (!FINAL.has(gen.status)) {
    if (Date.now() > deadline) throw new Stop(`${gen.id} is still ${gen.status}; poll it again later`);
    await sleep(POLL_INTERVAL_MS);
    gen = await call("GET", `/generations/${gen.id}`);
  }

  if (gen.status !== "succeeded") {
    // billing is "released": the hold went back to your balance
    throw new Stop(`${gen.status}, billing ${gen.billing}: ${JSON.stringify(gen.error)}`);
  }

  // 4. Download: outputs[].url is relative to the API origin and answers 302 with a signed file URL.
  //    Follow it yourself so your API key is never sent to the file host.
  const [output] = gen.outputs;
  if (output.state === "blocked") throw new Stop("Output blocked by moderation (the run is charged); contact support for a manual review");
  const hop = await fetch(ORIGIN + output.url, { headers: HEADERS, redirect: "manual", signal: AbortSignal.timeout(TIMEOUT_MS) });
  const location = hop.headers.get("Location");
  if (hop.status < 300 || hop.status > 399 || !location) throw new Stop(`GET ${output.url} -> ${hop.status}`);
  const file = await fetch(new URL(location, ORIGIN + output.url), { signal: AbortSignal.timeout(120_000) });
  if (!file.ok) throw new Stop(`download -> ${file.status}`);
  await writeFile("output.mp4", Buffer.from(await file.arrayBuffer()));
  console.log(`Saved output.mp4, charged $${usd(gen.amount)}`);
}

main().catch((err) => {
  console.error(err instanceof Stop ? err.message : err);
  process.exit(1);
});

여러 생성을 동시에 실행하려면 병렬로 시작하십시오(예: 스레드 풀, 비동기 HTTP 클라이언트를 쓰는 asyncio, 또는 Promise.all). 계정마다 기본적으로 동시에 진행 중인 생성은 2개입니다. 이를 넘으면 API가 Retry-After와 함께 429 concurrency_limit_reached를 반환하며, 두 예제의 call()은 이 시간만큼 기다린 뒤 다시 시도합니다. 한도를 올리려면 고객 지원에 문의하십시오.

인증

GET /v1/models와 GET /v1/pricing을 제외한 모든 요청은 Authorization 헤더에 API 키가 필요합니다. 키는 mn- 뒤에 영문자와 숫자 48자가 붙은 형태입니다.

curl
curl https://api.makernice.com/v1/generations \
  -H "Authorization: Bearer $MAKERNICE_API_KEY"
  • 전체 키는 만들 때 한 번만 표시됩니다. Makernice는 지문(fingerprint)만 저장하므로 잃어버린 키는 복구할 수 없습니다. 새 키를 만들고 기존 키를 삭제하십시오.
  • 키가 없거나 형식이 잘못되었거나 알 수 없거나 비활성화·만료·삭제된 경우에는 항상 같은 401 invalid_api_key 응답을 받습니다. 키를 비활성화하거나 삭제하면 다음 요청부터 적용됩니다.
  • API는 서버에서 호출하십시오. 교차 출처 브라우저 요청을 받지 않으므로(CORS 미지원) 프런트엔드 코드에 키를 넣지 마십시오.
  • 한 계정의 모든 키는 같은 생성을 봅니다. 어떤 키로든 콘솔에서 만든 것을 포함한 계정의 모든 생성을 조회, 목록 확인, 취소할 수 있습니다.
  • 콘솔에서 키마다 만료일과 지출 한도를 설정할 수 있습니다. 키의 지출액은 그 키로 만든 생성의 차감 금액과 현재 보류 중인 금액의 합계이며, 한도를 넘게 되는 생성 요청은 402 key_spend_limit_reached를 받습니다. 실패하거나 취소된 생성은 포함되지 않습니다.
  • 한 계정의 모든 키는 같은 선불 잔액에서 차감됩니다. 현재 Makernice 계정 하나에는 로그인이 하나만 있으며, 팀은 여러 API 키로 하나의 잔액을 함께 사용합니다.
  • API 키 페이지에서 키별 누적 지출을, 사용량 페이지에서 키와 모델별 API 지출을 확인할 수 있습니다.

요금 및 결제

  • 요금은 미국 달러로 청구됩니다. 모든 금액은 micro-USD(100만분의 1달러) 단위의 정수 문자열입니다. 1,000,000 = $1.00입니다. 부동소수점 숫자가 아니라 정수나 십진수(decimal)로 파싱하십시오.
  • 영상은 초당 가격이 매겨지고(수량은 요청한 길이이며, 참조 영상 클립이 있는 템플릿은 클립의 초도 포함), 이미지는 요청당 가격이 매겨집니다(생성 1건에 이미지 1장). 가격 = 단가 × 수량입니다.
  • 견적은 가격을 12분 동안 고정합니다(expires_at). 그사이 가격이 바뀌더라도 생성에는 견적 금액이 차감됩니다.

GET /v1/pricing은 현재 가격표 버전의 단가를 나열합니다. API 키가 필요 없으며 최대 60초 동안 캐시될 수 있습니다. official_unit_price가 있으면 같은 조합의 공식 정가로, 비교용일 뿐입니다. 실제로는 항상 unit_price가 청구됩니다.

GEThttps://api.makernice.com/v1/pricing
응답 예시(예시 값)
{
  "price_version": 3,
  "data": [
    { "model": "seedance-2.5", "mode": "t2v", "resolution": "720p", "unit": "second", "unit_price": "100000", "unit_price_usd": "0.10", "official_unit_price": "120000" }
  ]
}

실패한 생성도 차감됩니까?

아니요. 보류가 전액 해제되며, 실패한 생성은 차감되지 않습니다. 결과물이 정책 위반으로 검수에서 차단된 경우에는 차감됩니다. 고객 지원에 수동 검토를 요청할 수 있습니다.

생성을 만들면 견적 금액이 보류됩니다. 보류 금액은 생성이 완료될 때 차감으로 전환되며, 실패하거나 타임아웃되면 전액 해제되고, 대기열에서 취소한 경우에도 전액 해제됩니다. 해제는 환불이 아닙니다. 그 돈은 잔액에서 빠져나간 적이 없습니다.

상황billing차감 여부
생성이 성공함charged차감됨, 견적 금액 그대로
생성이 실패함(error.code가 generation_failed, upstream_unavailable 또는 invalid_request)released차감되지 않음
최대 실행 시간을 넘김(error.code: timeout)released차감되지 않음
queued 상태에서 취소함released차감되지 않음
생성을 만들 때 프롬프트가 차단됨(422 content_rejected, reason: prompt)—차감되지 않음. 생성이 만들어지지 않고 보류도 없음
입력 이미지 또는 모델 제공사의 안전 정책이 거부함(content_rejected, reason이 input_image 또는 model)released차감되지 않음
완성된 결과물이 결과물 검수에서 차단됨(결과물 state: blocked)charged차감됨. 고객 지원에 수동 검토를 요청할 수 있음
결과를 확인하는 중(status: checking)held성공으로 확정된 경우에만 차감됨

청구 사례

견적 받기

생성 입력을 보내면 응답에서 그 생성에 드는 정확한 비용을 알려 줍니다. 견적 하나는 생성 1건에 사용할 수 있습니다.

POSThttps://api.makernice.com/v1/generations/quote
필드타입설명
modelstring필수. GET /v1/models의 모델 id.
modestring필수. t2v, i2v, flf, r2v, t2i 또는 i2i. 모델마다 지원하는 모드가 나열되어 있습니다.
promptstring필수. 기본 1–4,000자이며, 일부 모델은 더 짧습니다(GET /v1/models의 maxPromptChars 참조).
durationinteger영상 전용. 모델 범위 내의 초 단위 값이며, 생략하면 모델 기본값을 씁니다.
resolutionstring모델의 resolutions 중 하나. 생략하면 defaultResolution을 씁니다. 모델에 나열된 값이 없으면 보낼 수 없습니다.
aspect_ratiostring모드의 aspectRatios 중 하나.
image_urlstring첫 프레임. i2v와 flf에서 필수.
last_image_urlstring마지막 프레임. flf에서 필수.
reference_imagesstring[]r2v와 i2i에서 필수. 최대 모드의 maxReferences개까지.
seedinteger선택. 0–2,147,483,647. 같은 설정에 같은 seed를 쓰면 더 비슷한 결과가 나옵니다.
generate_audioboolean선택. 영상에 사운드트랙을 넣을지 여부.
prompt_expansionstring선택. off, balanced 또는 quality. 모델이 프롬프트를 얼마나 고쳐 쓸 수 있는지 정합니다.
negative_promptstring선택. 피하고 싶은 요소를 최대 2,000자로 설명합니다.
watermarkboolean선택. 제공업체의 AI 생성 워터마크를 추가합니다.

마지막 다섯 개 필드는 선택 옵션입니다. 모델마다 그중 일부만 지원합니다. GET /v1/models의 prices[].options에 모드와 해상도별로 사용 가능한 옵션이 허용 값, 기본값과 함께 나열됩니다. 어떤 모델 경로에도 적용할 수 없는 필드는 400 invalid_request를 반환하며, 선택 필드를 바꿀 때도 다른 필드와 마찬가지로 새 견적이 필요합니다.

이미지는 공개 https URL(최대 2,048자)로 보냅니다. 모드에서 쓰지 않는 필드와 알 수 없는 필드는 400 invalid_request로 거부됩니다. 입력 필드의 경우 error.details에 각 문제가 { path, message } 형태로 나열됩니다(그 밖의 invalid_request 오류는 details 형태가 다르거나 없을 수 있습니다). 가격이 없는 조합은 422 price_unavailable를 반환합니다.

각 이미지는 공개적으로 접근 가능한 https URL이어야 하며, 그 URL에서 직접 정지 JPEG, PNG 또는 WebP 이미지(애니메이션 WebP와 GIF는 지원하지 않음)를 최대 10 MB로 제공해야 합니다. 리디렉션은 따라가지 않습니다. Makernice는 각 이미지를 한 번 가져와 사본을 보관하며, 콘텐츠 검수와 모델 모두 그 사본을 사용합니다. 이미지를 사용할 수 없으면 생성이 invalid_request로 실패하고, error.input에 해당 이미지가 표시되며, 보류는 해제됩니다.

curl
curl https://api.makernice.com/v1/generations/quote \
  -H "Authorization: Bearer $MAKERNICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"seedance-2.5","mode":"t2v","prompt":"온실 안을 천천히 지나가는 돌리 숏","duration":5,"resolution":"720p"}'
응답 예시(예시 값)
{
  "id": "qt_6f1c…",
  "amount": "500000",
  "price_version": 3,
  "expires_at": "2026-10-09T09:24:41.000Z",
  "facts": { "model": "seedance-2.5", "mode": "t2v", "resolution": "720p", "variant": "default", "unit": "second", "quantity": 5 }
}

견적은 가격을 얼마나 고정합니까?

모든 견적은 12분간 가격이 고정되며, 생성에는 항상 견적 금액 그대로 차감됩니다.

필드 또는 경우의미
expires_at견적을 만든 시각으로부터 12분 뒤입니다. 그 전에 생성을 만드십시오.
amount단가 × facts.quantity(영상은 초, 이미지는 1)이며 단위는 micro-USD입니다. 생성을 만들 때 보류되고, 생성이 성공하면 차감됩니다.
price_version견적을 받은 가격표 버전입니다. 생성을 만들기 전에 새 가격표가 게시되어도 견적 금액이 차감됩니다.
12분이 지난 경우이 견적으로 생성을 만들면 410 quote_expired를 반환합니다. 보류된 금액은 없으며, 새 견적을 받으십시오.
모델, 모드, 해상도 또는 길이를 바꾼 경우422 quote_mismatch를 반환합니다. 프롬프트만 바꾸면 견적은 그대로 유효합니다.
이미 사용한 견적409 quote_already_used를 반환합니다. 견적 하나로 생성 하나만 만들 수 있습니다.

생성 요청

견적과 같은 입력으로 생성을 만듭니다. Idempotency-Key 헤더는 필수입니다(멱등성 참조).

POSThttps://api.makernice.com/v1/generations
curl
curl https://api.makernice.com/v1/generations \
  -H "Authorization: Bearer $MAKERNICE_API_KEY" \
  -H "Idempotency-Key: 7c1e9a2b-order-1842" \
  -H "Content-Type: application/json" \
  -d '{"quote_id":"qt_6f1c…","input":{"model":"seedance-2.5","mode":"t2v","prompt":"온실 안을 천천히 지나가는 돌리 숏","duration":5,"resolution":"720p"}}'
  • 생성(status: queued, billing: held)과 함께 202로 응답합니다. 같은 Idempotency-Key로 다시 시도하면 원래 생성과 함께 200으로 응답합니다.
  • 프롬프트는 바꿀 수 있지만, 가격에 영향을 주는 항목(모델, 모드, 해상도, 길이)을 바꾸면 422 quote_mismatch를 반환합니다.
  • 이미 사용한 견적은 409 quote_already_used, 만료된 견적은 410 quote_expired, 계정에 없는 견적은 404 quote_not_found를 반환합니다.
  • 잔액이 부족하면 402 insufficient_balance, 키의 지출 한도를 넘으면 402 key_spend_limit_reached를 반환합니다.
  • 최상위 callback_url은 선택 항목입니다(https, 최대 2048자, URL에 자격 증명 포함 불가, 사설 또는 예약 주소 불가). 생성이 끝나면 그 주소로 생성을 POST 합니다. 계정의 웹훅 주소보다 우선합니다. 웹훅을 참조하십시오.

상태 확인

생성이 최종 상태에 도달할 때까지 폴링하십시오. 몇 초 간격으로 폴링해도 됩니다. 요청 한도의 읽기 한도를 참조하십시오. 영상 생성은 몇 분이 걸릴 수 있으며, 긴 영상이나 높은 해상도는 더 오래 걸립니다. 최대 실행 시간을 넘기면 생성이 실패하고 보류가 전액 해제됩니다.

GEThttps://api.makernice.com/v1/generations/{id}
상태의미
queued시작 대기 중입니다. 아직 취소할 수 있습니다.
generating모델이 생성 중입니다.
moderating생성이 끝나 금액이 차감되었습니다. 결과물이 안전 검사 중이며, 검사가 끝날 때까지 outputs는 비어 있습니다. 계속 폴링하십시오.
succeeded완료. 금액이 차감되었고 outputs가 준비되었습니다.
failed완료되지 못했습니다. 보류가 해제되며, error에 코드와 메시지가 들어 있습니다.
canceled대기 중에 취소되었습니다. 보류가 해제됩니다.
checking결과를 확인하는 중입니다. 확정될 때까지 금액은 보류된 상태로 유지됩니다.

실패한 생성의 error.code는 invalid_request(모델이 요청을 거부함. 프롬프트나 설정을 조정하십시오), content_rejected(콘텐츠 검수 또는 모델 제공업체의 안전 정책에 의해 거부됨. 보류는 해제됩니다), timeout, upstream_unavailable, generation_failed 중 하나입니다. content_rejected의 경우 error.reason에 거부된 대상이 표시됩니다. prompt(프롬프트), input_image(참조 이미지), model(모델 제공업체의 안전 정책) 중 하나입니다. 제출한 프롬프트가 차단될 때의 422 응답에도 같은 reason이 들어 있습니다. 사용할 수 없는 참조 이미지 때문에 invalid_request가 발생하면 error.input에 해당 이미지가 표시됩니다. image_url, last_image_url 또는 reference_images[0](0부터 시작) 형식입니다. 그 이미지를 교체하고 새 생성을 만드십시오.

응답 예시(예시 값)
{
  "id": "gen_3b9e…",
  "type": "video",
  "model": "seedance-2.5",
  "mode": "t2v",
  "status": "succeeded",
  "prompt": "온실 안을 천천히 지나가는 돌리 숏",
  "params": { "duration": 5, "resolution": "720p", "aspectRatio": null },
  "amount": "500000",
  "billing": "charged",
  "price_version": 3,
  "outputs": [
    {
      "index": 0,
      "url": "/v1/generations/gen_3b9e…/outputs/0",
      "state": "stored",
      "contentType": "video/mp4",
      "expiresAt": "2026-11-08T09:14:03.000Z"
    }
  ],
  "error": null,
  "created_at": "2026-10-09T09:12:41.000Z",
  "finished_at": "2026-10-09T09:14:03.000Z"
}

생성이 끝나고 90일이 지나면 데이터 보존 정책에 따라 요청 내용이 삭제되어 prompt가 빈 문자열이 됩니다. 청구 정보(model, params, amount, status, 타임스탬프)는 유지됩니다.

GET /v1/generations/{id}/events는 생성의 타임라인을 반환합니다. 코드: created, submitted, generating, delivered, failed_refunded, canceled_refunded, checking, requeued.

GEThttps://api.makernice.com/v1/generations/{id}/events
응답 예시(예시 값)
{
  "data": [
    { "code": "created", "at": "2026-10-09T09:12:41.000Z" },
    { "code": "submitted", "at": "2026-10-09T09:12:43.000Z" },
    { "code": "generating", "at": "2026-10-09T09:12:50.000Z" },
    { "code": "delivered", "at": "2026-10-09T09:14:03.000Z" }
  ]
}

결과물 다운로드

outputs의 각 항목에는 /v1/generations/gen_…/outputs/0처럼 https://api.makernice.com 기준 상대 경로인 url이 있습니다. API 키와 함께 요청하면 파일로 가는 단기 링크로의 302 리디렉션이 응답됩니다. 리디렉션 대상을 저장하지 말고, 파일이 필요할 때마다 엔드포인트를 다시 요청하십시오.

GEThttps://api.makernice.com/v1/generations/{id}/outputs/{n}
curl
curl -L -o video.mp4 https://api.makernice.com/v1/generations/gen_…/outputs/0 \
  -H "Authorization: Bearer $MAKERNICE_API_KEY"
  • ?download=1을 붙이면 파일이 첨부 파일로 제공됩니다. 파일이 stored 상태일 때 작동하며, pending 또는 degraded 상태에서는 원본 파일로 리디렉션되고 이 매개변수는 효과가 없습니다.
  • curl -L은 리디렉션 대상 호스트에 Authorization 헤더를 보내지 않으므로 키는 Makernice 밖으로 나가지 않습니다.
  • 결과물은 생성이 succeeded일 때만 존재합니다. 그 밖의 경우 엔드포인트는 404 output_not_found를 반환합니다.
  • state: pending(아직 저장 중이며 링크는 이미 작동함), stored(expiresAt까지 저장됨), degraded(저장이 완료되지 않아 링크가 더 일찍 작동을 멈출 수 있음 — 서둘러 다운로드하십시오), expired(저장된 파일이 만료됨. 엔드포인트가 410 output_expired를 반환함), blocked(결과물이 콘텐츠 검수를 통과하지 못함. url은 빈 문자열 — 수동 검토는 고객 지원에 문의하십시오).

취소

queued 상태의 생성만 취소할 수 있으며, 보류가 전액 해제되고 취소된 생성이 응답으로 반환됩니다. 생성이 generating상태가 되면 중지할 수 없고, 취소 요청은 409 not_cancelable을 반환합니다.

POSThttps://api.makernice.com/v1/generations/{id}/cancel
curl
curl -X POST https://api.makernice.com/v1/generations/gen_…/cancel \
  -H "Authorization: Bearer $MAKERNICE_API_KEY"

# → { "id": "gen_…", "status": "canceled", "billing": "released", … }

생성 목록

콘솔에서 만든 것을 포함해 계정의 생성을 최신순으로 나열합니다.

GEThttps://api.makernice.com/v1/generations
쿼리설명
limit페이지 크기, 1–100. 기본값 20.
cursor이전 페이지의 next_cursor.
status하나 이상의 상태를 쉼표로 구분. 예: queued,generating.
typevideo 또는 image.
model모델 id.
q생성 id의 일부 또는 프롬프트에 포함된 텍스트(둘 다 대소문자 구분 없음).
curl
curl "https://api.makernice.com/v1/generations?status=queued,generating&limit=50" \
  -H "Authorization: Bearer $MAKERNICE_API_KEY"

# → { "data": [ …생성 목록… ], "next_cursor": "gen_…" | null, "total": 12 }

total은 필터에 맞는 모든 생성의 수입니다. next_cursor가 null이면 마지막 페이지입니다.

웹훅

폴링 대신, 생성이 최종 상태에 도달하면 Makernice가 생성을 서버로 POST 하도록 할 수 있습니다. 생성을 만들 때 지정한 callback_url로 보내며, 지정하지 않았으면 계정의 웹훅 주소(콘솔 설정에서 설정)로 보냅니다. 둘 다 없으면 아무것도 보내지 않습니다.

이벤트전송 시점
generation.succeeded생성이 성공하여 차감되었을 때.
generation.failed생성이 실패하여 보류 금액이 해제되었을 때.
generation.canceled생성이 취소되어 보류 금액이 해제되었을 때.

요청

요청 예시(예시 값)
POST /your/webhook/path HTTP/1.1
Content-Type: application/json
Makernice-Signature: t=1791364443,v1=5f0c2d…e81a
Makernice-Event-Id: evt_1b7f…
본문 예시(예시 값)
{
  "id": "evt_1b7f…",
  "type": "generation.succeeded",
  "created_at": "2026-10-09T09:14:03.000Z",
  "data": {
    "id": "gen_3b9e…",
    "status": "succeeded",
    "billing": "charged",
    "outputs": [
      { "index": 0, "url": "https://api.makernice.com/v1/generations/gen_3b9e…/outputs/0", "state": "stored", … }
    ],
    …
  }
}
  • data는 GET /v1/generations/{id}와 같은 생성 객체이며, outputs[].url만 절대 URL입니다.
  • Makernice-Event-Id는 본문의 id와 같고 재시도 간에도 바뀌지 않습니다. 중복을 무시하는 데 사용하십시오.

서명 검증

Makernice-Signature는 t=<unix seconds>,v1=<hex> 형식이며, v1은 서명 시크릿으로 t + "." + raw body에 대해 계산한 HMAC-SHA256입니다. 받은 원본 바이트 그대로 계산하고, 상수 시간으로 비교하며, t가 서버 시각과 5분 넘게 차이 나는 요청은 거부하십시오. 서명 시크릿은 콘솔 설정에 있으며, callback_url 전송에도 같은 시크릿으로 서명합니다. 시크릿을 교체하면 아직 보내지 않은 전송은 새 시크릿으로 서명됩니다.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the body exactly as received (Buffer). Don't re-serialize parsed JSON.
export function verifyWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(signatureHeader.split(",").map((p) => [p.slice(0, p.indexOf("=")), p.slice(p.indexOf("=") + 1)]));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}
Python
import hashlib
import hmac
import time


# raw_body: the body exactly as received (bytes). Don't re-serialize parsed JSON.
def verify_webhook(raw_body: bytes, signature_header: str, secret: str, tolerance_seconds: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(",") if "=" in p)
    try:
        t = int(parts["t"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > tolerance_seconds:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

전송과 재시도

  • HTTPS만 지원합니다. 리디렉션은 따라가지 않습니다. 각 시도는 10초 후 시간 초과됩니다.
  • 2xx 응답이면 전송된 것으로 봅니다. 빠르게 응답하고 무거운 작업은 그 뒤에 처리하십시오.
  • 그렇지 않으면 1 min, 5 min, 30 min, 2 h, 6 h, 12 h 후에 재시도하며(총 7회 시도), 그래도 실패하면 전송 실패로 표시합니다. 최근 전송과 마지막 응답은 콘솔 설정에 표시됩니다.
  • 생성마다 보통 이벤트 하나를 보내며, 같은 전송이 두 번 이상 도착할 수 있습니다(예: 수신 측에서 시간 초과가 난 뒤). 콘텐츠 검토로 보류되었던 결과물이 나중에 공개되면 새 이벤트 id로 생성을 다시 보내므로 이벤트를 두 개 받을 수 있으며, 가장 최근 것이 유효합니다.

모델

GET /v1/models는 사용할 수 있는 모델과 모드, 해상도, 길이를 나열합니다. 가격이 있는 모드만 나열되며, 가격은 modes[].prices에 있습니다. API 키가 필요 없으며 최대 60초 동안 캐시될 수 있습니다.

GEThttps://api.makernice.com/v1/models
  • resolutions / defaultResolution: resolution에 허용되는 값. 목록이 비어 있으면 해상도가 고정된 모델이므로 resolution을 생략합니다(고정값이라도 보내면 400 invalid_request를 반환). 고정 등급은 modes[].prices[].resolution에 표시된 값입니다. 예를 들어 seedance-2.0-fast는 항상 720p로 렌더링합니다. 나열된 해상도라도 일부 모드에서는 가격이 없을 수 있으니 modes[].prices를 확인하십시오. 그런 해상도로 견적을 요청하면 422 price_unavailable를 반환합니다.
  • durations: 영상은 초 단위 { min, max, default }, 이미지는 null.
  • modes[].needs: 모드에 필요한 이미지(image → image_url, lastImage → last_image_url, references → reference_images, 최대 maxReferences개).
  • modes[].aspectRatios: 허용되는 aspect_ratio 값. modes[].prices: 해상도별 단가.
응답 예시(예시 값)
{
  "data": [
    {
      "id": "seedance-2.5",
      "type": "video",
      "resolutions": ["480p", "720p", "1080p", "4k"],
      "defaultResolution": "720p",
      "durations": { "min": 4, "max": 30, "default": 5 },
      "modes": [
        {
          "mode": "t2v",
          "needs": { "image": false, "lastImage": false, "references": false },
          "maxReferences": null,
          "aspectRatios": ["adaptive", "16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"],
          "prices": [{ "resolution": "720p", "unit": "second", "unitPrice": "100000" }]
        }
      ]
    }
  ]
}

어떤 모델을 호출할 수 있습니까?

API 키 하나로 ByteDance, Kuaishou, MiniMax, Alibaba의 영상 모델 13개와 이미지 모델 4개, 총 17개 모델을 모두 호출할 수 있으며, Seedance, Kling, Wan, MiniMax H3가 포함됩니다. 모든 모델이 같은 요청 형식을 쓰므로 model을 아래의 API 모델 id로 지정하면 됩니다.

모델제공사API 모델 id유형
Seedance 2.5ByteDanceseedance-2.5영상
Seedance 2.0ByteDanceseedance-2.0영상
Seedance 2.0 FastByteDanceseedance-2.0-fast영상
MiniMax H3MiniMaxminimax-h3영상
MiniMax H3 MaxMiniMaxminimax-h3-max영상
MiniMax H3 Max TurboMiniMaxminimax-h3-max-turbo영상
Wan 3.0Alibabawan-3.0영상
Wan 3.0 PrimeAlibabawan-3.0-prime영상
Kling 3.0 ProKuaishoukling-3.0-pro영상
Kling 3.0 StandardKuaishoukling-3.0-standard영상
Kling 3.0 4KKuaishoukling-3.0-4k영상
Kling 4.0Kuaishoukling-4.0영상
Happy Horse 1.1Alibabahappy-horse-1.1영상
Seedream 5.0 ProByteDanceseedream-5.0-pro이미지
Seedream 5.0ByteDanceseedream-5.0이미지
Qwen3 ImageAlibabaqwen3-image이미지
Qwen3 Pro ImageAlibabaqwen3-pro-image이미지

작업별 모델, 지원 모드, 예시 가격(영어 페이지): image to video, AI video generator, AI image generator.

오류

오류는 JSON입니다: { "error": { "code", "message", "reason"?, "details"?, "request_id" } }. 분기는 code로 하십시오. 메시지는 바뀔 수 있습니다. 모든 응답에는 x-request-id 헤더(request_id와 같은 값)가 포함되며, 고객 지원에 문의할 때 함께 알려 주시면 해당 요청을 찾을 수 있습니다.

상태코드조치
400invalid_request본문, 쿼리 또는 헤더가 유효하지 않습니다. details에 각 필드와 문제가 나열되어 있으니 요청을 수정하십시오.
400template_conflict템플릿 요청에는 template, images와 선택 항목인 prompt, resolution, aspect_ratio만 넣을 수 있습니다. 모델, 모드, 길이 등 나머지 설정은 템플릿이 정합니다. 다른 필드를 제거하십시오.
401invalid_api_key키가 없거나 형식이 잘못되었거나 비활성화·만료·삭제되었습니다.
402insufficient_balance지갑을 충전하십시오.
402key_spend_limit_reached키의 지출 한도를 올리거나 다른 키를 사용하십시오.
402daily_spend_limit_reached계정이 일일 지출 한도에 도달했습니다. 한도는 매일 00:00 UTC에 초기화됩니다. 한도를 올리려면 고객 지원에 문의하십시오.
403org_frozen계정이 동결되었습니다. 고객 지원에 문의하십시오.
404quote_not_found계정에 해당 견적이 없습니다. 새 견적을 받으십시오.
404task_not_found계정에 해당 생성이 없습니다. id를 확인하십시오.
404output_not_found생성이 아직 성공하지 않았거나 해당 index의 결과물이 없습니다.
404template_not_found이 id의 템플릿이 없습니다. GET /v1/templates로 사용 가능한 템플릿 목록을 확인하십시오.
404not_found알 수 없는 엔드포인트입니다. 메서드와 경로를 확인하십시오.
409quote_already_used새 견적을 받으십시오.
409not_cancelable대기 중(queued)인 생성만 취소할 수 있습니다.
410quote_expired새 견적을 받으십시오.
410output_expired저장된 파일이 만료되어 더 이상 다운로드할 수 없습니다.
410template_retired이 템플릿은 더 이상 제공되지 않습니다. GET /v1/templates에서 다른 템플릿을 고르십시오. 이미 만든 생성에는 영향이 없습니다.
413payload_too_large요청 본문이 64 KB를 넘습니다. 프롬프트를 줄이거나 이미지 URL 수를 줄이십시오.
422idempotency_conflict이 Idempotency-Key는 다른 요청에 이미 사용되었습니다. 새 키를 사용하십시오.
422quote_mismatch입력이 가격에 영향을 주는 항목(모델, 모드, 해상도, 길이)을 바꿨습니다. 이 입력으로 다시 견적을 받으십시오.
422price_unavailable이 조합에는 가격이 없습니다. GET /v1/models에 나열된 조합을 고르십시오.
422content_rejected콘텐츠 검수 또는 모델 제공업체의 안전 정책에 의해 거부되었습니다. error.reason에 거부된 대상(prompt, input_image 또는 model)이 표시됩니다. 프롬프트나 이미지를 바꾸십시오. 이번 요청은 차감되지 않았습니다.
429rate_limitedRetry-After에 표시된 초만큼 기다린 뒤 다시 시도하십시오.
429concurrency_limit_reached계정에서 진행 중인 생성이 너무 많습니다. 하나가 끝날 때까지 기다리거나(Retry-After 참조) 고객 지원에 한도 상향을 문의하십시오.
500internal_error예기치 않은 서버 오류입니다. 백오프를 두고 다시 시도하십시오. 고객 지원에 문의할 때는 request_id를 함께 알려 주십시오.

예기치 않은 5xx 응답이나 네트워크 오류는 백오프를 두고 다시 시도하십시오. 생성 요청을 다시 시도할 때는 같은 Idempotency-Key를 재사용하면 두 번 차감되지 않습니다.

요청 한도

한도는 분당 기준입니다. 초과하면 Retry-After 헤더(초)와 함께 429 rate_limited를 반환합니다.

범위한도
키별 — 쓰기(견적, 생성, 취소)60 / 분
키별 — 읽기(조회, 목록, 이벤트, 결과물)600 / 분
계정별 — 모든 키의 쓰기 합계120 / 분
IP별 — models, pricing 및 거부된 키로 보낸 요청120 / 분

또한 키 하나당 동시에 진행 중인 생성 요청은 최대 4개이며, 그 이상도 429를 반환합니다. 한도는 조정될 수 있습니다.

계정마다 사용 한도도 두 가지 있으며, 기본값은 동시에 진행 중인 생성 2개와 UTC 기준 하루 US$100 지출입니다. 초과하면 429 concurrency_limit_reached(Retry-After 포함) 또는 402 daily_spend_limit_reached를 반환합니다. 한도를 올리려면 고객 지원에 문의하십시오. 생성은 queued, generating 또는 checking 상태인 동안 어떤 모델이나 키로 만들었는지와 관계없이 동시 진행 한도에 포함됩니다. moderating, succeeded, failed 또는 canceled에 도달하면 더 이상 포함되지 않습니다. 웹 앱에서 시작한 생성도 같은 한도에 포함됩니다.

멱등성

  • POST /v1/generations에는 Idempotency-Key 헤더가 필요합니다. 공백 없는 출력 가능한 ASCII 문자 1–200자입니다. 새 생성마다 UUID 같은 새 값을 사용하십시오.
  • 키는 계정 안에서 고유하며, 계정의 모든 API 키가 공유합니다.
  • 같은 키, 같은 견적, 같은 입력으로 요청을 반복하면 원래 생성이 200으로 반환되며 다시 차감되지 않습니다.
  • 같은 키를 다른 quote_id, 다른 input(프롬프트와 이미지 URL 포함) 또는 다른 callback_url과 함께 사용하면 422 idempotency_conflict를 반환합니다.
  • 견적과 취소에는 Idempotency-Key가 필요 없습니다. 이미 성공한 취소를 반복하면 409 not_cancelable을 반환하니, GET으로 상태를 확인하십시오.

자주 묻는 질문

견적에도 비용이 듭니까?
아니요. POST /v1/generations/quote는 입력값의 가격만 계산합니다. 그 견적으로 생성을 요청하기 전까지는 보류도 차감도 일어나지 않습니다.
모델마다 API 키를 따로 만들어야 합니까?
아니요. API 키 하나로 17개 모델을 모두 호출할 수 있습니다. 모델을 바꾸려면 요청의 model 필드를 변경하면 됩니다.