이미지 생성 요청이 접수되었다고 해서 이미지가 완성된 것은 아닙니다. 애플리케이션은 작업 ID를 보관하고 동일한 작업의 상태를 확인해야 합니다. 요청이 실패하거나 중단되었을 때도 실수로 새 작업을 시작하지 않도록 처리해야 합니다.
이 튜토리얼에서는 이 과정을 수행하는 작은 서버 측 Node.js 클라이언트를 만듭니다. 명령은 submit과 resume으로 나뉩니다. 전자는 작업을 생성하고, 후자는 기존 작업만 조회합니다. 홈페이지 배너에서 복사한 모델 이름이 아니라 API에서 지원하는 모델 ID를 먼저 확인하세요.
이 튜토리얼은 텍스트로 이미지를 생성하는 방법을 다룹니다. 참조 파일 업로드, 브라우저 인터페이스 구현, 모델 품질 측정은 다루지 않습니다. 검증에는 로컬 모의 테스트 29개와 2026년 9월 4일에 통제된 조건에서 실행한 프로덕션 이미지 작업 1건을 사용했습니다. 해당 작업은 이미지 1개를 반환했고, API 요금으로 120포인트가 청구된 기록을 확인했습니다. 이는 특정 날짜에 수행한 연동 확인 1건이며, 속도 벤치마크나 모든 경우에 적용되는 가격, 다른 계정의 이용 가능 여부를 보장하는 근거가 아닙니다.
표지 이미지는 2026년 9월 4일에 캡처한 모델 카탈로그 화면입니다. 특정 시점의 예시 자료로 참고하세요. 현재 가격 견적이나 이용 가능 여부를 보장하는 자료가 아닙니다.
핵심 요약
- API 키는 서버에 보관하고, 브라우저 번들이나 공개 저장소에 포함하지 마세요.
- 이미지 작업의 경우, 접수 응답에서 숫자형
data.taskId를 읽고 이후 조회를 위해 보관하세요.- 저장한 작업을
ready또는failed상태가 될 때까지 폴링하세요. 성공한 뒤에만data.images에서 이미지 URL을 읽으세요.- 클라이언트에서 시간 초과가 발생했다는 사실만으로 원격 작업이 실패했거나 취소되었다고 판단할 수 없습니다. 작업 ID가 있으면 조회를 재개하고, 생성 요청을 무작정 반복하지 마세요.
가이드 목차
시작하기 전에
Node.js, API 키, API 결제가 설정된 계정을 준비하고 비공개 터미널 또는 서버 환경을 사용하세요. 함께 제공되는 예제는 내장 fetch와 JavaScript 모듈을 사용하므로 서드파티 패키지를 설치할 필요가 없습니다. Node의 전역 API 문서에서 fetch와 AbortSignal.timeout을 설명합니다. 클라이언트는 이를 사용해 개별 요청의 시간을 제한합니다. 로컬 검증은 Windows와 Node.js v22.22.1 환경에서 수행했습니다. 이는 테스트 환경에 대한 기록이며, 다른 모든 버전에서도 테스트했다는 의미는 아닙니다.
파일 편집, 환경 변수 설정, JSON 읽기에 익숙해야 합니다. 계속하기 전에 설치된 런타임을 확인하세요.
node --version
이 명령은 설치된 버전을 출력하며, 생성 API에 접속하지 않습니다. 뒤에서 제공하는 전체 클라이언트를 작업 폴더 안의 examples/pixmind-image.mjs로 저장한 다음, 해당 폴더에서 명령을 실행하세요.
고객 데이터나 기밀 자료가 없는 무해한 테스트 프롬프트를 선택하세요. 이 튜토리얼에서는 스튜디오 배경의 도자기 커피잔을 사용하므로 참조 파일을 업로드할 필요가 없습니다. 제출하기 전에 선택한 모델의 현재 API 가격을 확인하세요. Studio 크레딧, 구독, API 결제를 서로 바꾸어 사용할 수 있다고 가정해서는 안 됩니다. 해당 API 상품에 대한 설명은 모델 카탈로그에서 확인할 수 있습니다.
대화형 방식으로 이미지만 만들고 싶다면 Image Agent 가이드에서 해당 워크플로를 확인하세요. 여기서 만드는 클라이언트는 모델 ID를 명시적으로 전달하고 응답을 직접 처리하는 애플리케이션을 위한 것입니다.
모델 선택 및 API 키 설정
API에서 지원하는 모델 ID와 해당 모델이 지원하는 매개변수를 사용하세요. 예제에서는 API 빠른 시작의 이미지 요청과 동일하게 nano-banana-pro를 선택하고 aspectRatio: "1:1", resolution: "1K"를 사용합니다. 인증된 GET /models 요청과 프로덕션 테스트를 통해 2026년 9월 4일 기준 테스트 계정에서 이 조합을 사용할 수 있음을 확인했습니다. 실제 실행에 앞서 본인 계정의 이용 가능 여부와 매개변수 지원을 다시 확인하세요. 모델 문자열만 바꾼다고 나머지 요청이 그대로 유효하다는 보장은 없습니다.
API 콘솔에서 키를 만든 뒤 서버 프로세스에 PIXMIND_API_KEY로 전달하세요. 사용할 수 있다면 비공개 비밀 정보 관리 체계를 활용하세요. 다음 예제는 실제로 작동하는 인증 정보가 아니라 자리표시자를 사용해 환경 변수 문법을 보여줍니다.
PowerShell의 경우:
$env:PIXMIND_API_KEY = "REPLACE_WITH_YOUR_PRIVATE_API_KEY"
$env:PIXMIND_MODEL = "nano-banana-pro"
$env:PIXMIND_PROMPT = "A ceramic coffee cup on a plain studio background, soft side lighting, no text"
Bash의 경우:
export PIXMIND_API_KEY="REPLACE_WITH_YOUR_PRIVATE_API_KEY"
export PIXMIND_MODEL="nano-banana-pro"
export PIXMIND_PROMPT="A ceramic coffee cup on a plain studio background, soft side lighting, no text"
이 할당문은 현재 셸과 이 셸에서 실행하는 프로세스를 설정하며, 작업을 생성하지 않습니다. 실제 비밀 정보를 입력할 때는 셸 기록과 녹화·기록 내용을 보호하고, 해당 정보를 소스 관리 시스템에 절대로 커밋하지 마세요. 개발용 키와 프로덕션용 키의 분리, 노출 후 키 교체에 관한 내용은 인증 가이드를 따르세요.
이 튜토리얼에서 사용하는 기본 URL은 다음과 같습니다.
https://aihub-admin.aimix.pro/api-platform/v1
이 기본 URL 뒤에 /generations 또는 /tasks/{taskId}를 붙이세요. /v1을 한 번 더 붙이지 마세요. 모델 목록은 GET /models로 조회할 수 있습니다. 다만 매개변수를 선택하기 전에 모델별 기능을 살펴보려면 카탈로그가 더 편리합니다.
cURL로 첫 이미지 생성 요청 제출하기
cURL 요청은 Node.js의 submit 명령을 대신하는 방법이며, 먼저 실행해야 하는 설정 단계가 아닙니다. 두 방법 모두 생성 요청을 제출합니다. 둘 다 실행하면 과금 대상 작업이 2개 생성될 수 있습니다. cURL로 제출했다면 이후에는 반환된 작업 ID로 Node.js의 resume 명령을 사용하세요.
이 예제는 Bash 문법을 사용합니다. PowerShell에서는 Bash의 줄 연결 구문을 터미널에 붙여 넣지 말고 아래의 Node.js 클라이언트를 사용하세요.
curl --connect-timeout 10 --max-time 30 \
--request POST \
'https://aihub-admin.aimix.pro/api-platform/v1/generations' \
--header "Authorization: Bearer $PIXMIND_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"model": "nano-banana-pro",
"type": "image",
"prompt": "A ceramic coffee cup on a plain studio background, soft side lighting, no text",
"aspectRatio": "1:1",
"resolution": "1K"
}'
이 cURL 본문의 프롬프트와 매개변수는 리터럴 값입니다. PIXMIND_PROMPT를 변경해도 이 JSON은 바뀌지 않습니다. 해당 환경 변수는 Node.js 클라이언트에서 읽습니다. 이 차이를 이해하면 실제로 보내려는 요청과 그 주변의 셸 설정을 구분할 수 있습니다.
아래는 일부 필드를 생략한 접수 응답 예시입니다. ID는 프로덕션 테스트의 ID가 아닌 자리표시자입니다. 12345는 독자가 조회해야 할 작업이 아닙니다.
{
"code": 1000,
"message": "success",
"data": {
"id": "img_12345",
"taskId": 12345
}
}
테스트한 프로덕션 미디어 응답은 성공 데이터를 code, message, data, timestamp가 있는 객체로 감싸서 반환했습니다. 위 예시에서는 타임스탬프와 다른 필드를 생략했습니다. 이 워크플로에서 성공으로 판단하려면 HTTP 응답이 성공이어야 하고 애플리케이션 응답도 유효해야 합니다. data.id가 아니라 data.taskId를 읽으세요. 전자에는 img_12345와 같이 접두사가 붙은 문자열이 들어갈 수 있지만, 작업 조회 경로에서는 숫자형 ID를 사용합니다.
숫자형 ID를 즉시 저장하세요. ID를 받기 전에 요청 시간이 초과되면 멈추고, 계정의 작업 기록이나 지원팀을 통해 제출 결과를 확인하세요. 실제로 어떤 일이 일어났는지 확인하는 대신 POST를 반복하는 것은 안전하지 않습니다.
작업 폴링 및 이미지 URL 읽기
기존 작업이 종료 상태에 도달할 때까지 조회하세요. 비동기 작업 문서에서는 pending, processing, ready, failed를 설명합니다. 이 폴링 워크플로를 종료하는 상태는 마지막 두 가지뿐입니다.
Submit once
|
Save numeric taskId
|
GET /tasks/{taskId} <--- wait, then query again
| ^
+--- pending / processing ---+
|
+--- ready ---> read images, stop
|
+--- failed ---> report failure, stop
Bash에서 직접 조회하려면 자리표시자 ID를 본인의 작업 ID로 바꾸세요.
TASK_ID="REPLACE_WITH_YOUR_NUMERIC_TASK_ID"
curl --connect-timeout 10 --max-time 30 \
"https://aihub-admin.aimix.pro/api-platform/v1/tasks/$TASK_ID" \
--header "Authorization: Bearer $PIXMIND_API_KEY"
현재 미디어 응답 규약에서 필요한 필드는 각각 다음 역할을 합니다.
| 필드 | 이 클라이언트에서의 의미 |
|---|---|
data.taskId |
기존 작업을 조회하는 데 사용하는 숫자형 식별자 |
data.status |
계속 기다릴지, 결과를 읽을지, 실패로 중단할지를 결정하는 상태 |
data.images |
ready 이후에 사용하는 이미지 출력 URL 배열 |
data.videoUrl |
동영상 출력 필드이며, 이미지 결과 배열이 아님 |
ready 응답이라도 사용할 수 있는 이미지 URL이 없다면 애플리케이션에서 이미지 생성 성공으로 처리할 수 없습니다. 조사할 수 있도록 작업 ID와 함께 이 불일치를 알리세요. 예시 URL로 대체하거나 이미지를 다운로드했다고 보고하지 마세요.
클라이언트는 반환된 이미지 URL을 출력할 뿐, 이미지 파일을 가져오거나 보관하지는 않습니다. 애플리케이션에 장기 보관이 필요하다면 별도 단계로 설계하고, 관련 에셋 보존 및 이용 약관을 확인하세요. URL이 있다는 이유만으로 영구 보관이 보장된다고 추정하지 마세요.
전체 Node.js 예제 실행하기
새 작업 1건을 만들려면 submit을, 기존 작업을 조회하려면 resume을 사용하세요. 인수 없이 스크립트를 실행하면 사용법만 출력하고 API는 호출하지 않습니다. 이 기본 동작은 일반적인 재시작이나 CLI를 간단히 살펴보는 과정에서 새 작업이 생성되는 것을 막습니다.
아래 전체 소스를 examples/pixmind-image.mjs로 저장하세요.
전체 Node.js 소스: pixmind-image.mjs
import { pathToFileURL } from 'node:url';
const BASE_URL = 'https://aihub-admin.aimix.pro/api-platform/v1';
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
export class ApiError extends Error {
constructor(message, { status = 0, transient = false, retryAfterMs = 0 } = {}) {
super(message);
Object.assign(this, { status, transient, retryAfterMs });
}
}
export function parseTaskId(value) {
if (!/^\d+$/.test(String(value))) throw new Error('Use a numeric taskId, not img_...');
const id = Number(value);
if (!Number.isSafeInteger(id) || id <= 0) throw new Error('Invalid taskId');
return id;
}
export function retryAfter(value, now) {
if (!value) return 0;
if (/^\d+(\.\d+)?$/.test(value)) return Number(value) * 1000;
const date = Date.parse(value);
return Number.isFinite(date) ? Math.max(0, date - now) : 0;
}
export function createClient({
apiKey,
fetchImpl = fetch,
now = Date.now,
wait = sleep,
random = Math.random,
requestTimeoutMs = 30_000,
pollTimeoutMs = 300_000,
maxConsecutiveErrors = 5,
} = {}) {
if (typeof apiKey !== 'string' || !apiKey.trim()) throw new Error('Set PIXMIND_API_KEY');
async function request(path, method, payload, timeoutMs = requestTimeoutMs) {
let response;
let body;
try {
response = await fetchImpl(`${BASE_URL}${path}`, {
method,
redirect: 'error',
headers: {
Authorization: `Bearer ${apiKey}`,
...(payload ? { 'Content-Type': 'application/json' } : {}),
},
...(payload ? { body: JSON.stringify(payload) } : {}),
signal: AbortSignal.timeout(Math.max(1, Math.ceil(timeoutMs))),
});
// Read the body inside the timeout/network error boundary as well.
body = await response.text();
} catch {
throw new ApiError('Network error or request timeout', { transient: true });
}
let envelope;
try { envelope = JSON.parse(body); } catch { /* handle below */ }
const transient = response.status === 429 || response.status >= 500;
if (!response.ok) {
// Do not echo arbitrary server bodies, prompts, credentials, or output URLs.
throw new ApiError(`HTTP ${response.status}; inspect the account and request`, {
status: response.status,
transient,
retryAfterMs: retryAfter(response.headers.get('retry-after'), now()),
});
}
if (!envelope || typeof envelope !== 'object') {
throw new ApiError('Expected a JSON API response');
}
if (envelope.code !== 1000 || !envelope.data) {
throw new ApiError('API returned an unsuccessful or incomplete envelope');
}
return envelope.data;
}
async function submit({ model = 'nano-banana-pro', prompt } = {}) {
if (typeof model !== 'string' || !model.trim()) throw new Error('A model is required');
if (typeof prompt !== 'string' || !prompt.trim()) throw new Error('A prompt is required');
try {
const data = await request('/generations', 'POST', {
model, type: 'image', prompt, aspectRatio: '1:1', resolution: '1K',
});
// The public media contract returns a number, not the prefixed display ID.
if (typeof data.taskId !== 'number') throw new Error('Missing numeric taskId');
return parseTaskId(data.taskId);
} catch (error) {
// A timeout or malformed response does not prove that creation failed.
throw new Error(`Submission not confirmed: ${error.message}. No automatic retry was made. Check task records before submitting again.`);
}
}
async function poll(taskId) {
const id = parseTaskId(taskId);
const deadline = now() + pollTimeoutMs;
let attempts = 0;
let errors = 0;
const timedOut = () => new Error(`Stopped waiting for task ${id}; it may still be running. Resume this ID later.`);
while (now() < deadline) {
let retryFloor = 0;
let task;
try {
task = await request(`/tasks/${id}`, 'GET', undefined,
Math.min(requestTimeoutMs, deadline - now()));
errors = 0;
} catch (error) {
if (now() >= deadline) throw timedOut();
if (!(error instanceof ApiError) || !error.transient) throw error;
errors += 1;
if (errors >= maxConsecutiveErrors) {
throw new Error(`Stopped after ${errors} consecutive query errors for task ${id}; resume this ID later.`);
}
retryFloor = error.retryAfterMs;
}
if (now() >= deadline) throw timedOut();
if (task) {
if (task.taskId !== id) throw new Error('Task response ID does not match the requested task');
if (task.status === 'failed') throw new Error(`Task ${id} failed; inspect its record before creating another task.`);
if (task.status === 'ready') {
if (!Array.isArray(task.images) || task.images.length === 0 ||
!task.images.every(url => {
try { return ['https:', 'http:'].includes(new URL(url).protocol); }
catch { return false; }
})) throw new Error(`Task ${id} is ready but has no valid image URLs`);
return task.images;
}
if (!['pending', 'processing'].includes(task.status)) {
throw new Error(`Task ${id} returned an unrecognized status; inspect its record.`);
}
}
// Client policy, not a PixMind latency guarantee or server-side retry feature.
const ceiling = Math.min(10_000, 1_000 * 2 ** Math.min(attempts++, 4));
const delay = Math.max(retryFloor, ceiling * (0.5 + 0.5 * random()));
const remaining = deadline - now();
if (delay >= remaining) {
// Never poll earlier than Retry-After just to fit the local deadline.
await wait(Math.max(0, remaining));
throw timedOut();
}
await wait(delay);
}
throw timedOut();
}
return { submit, poll };
}
export async function main(args = process.argv.slice(2), env = process.env, deps = {}) {
const log = deps.log ?? console.log;
const [mode, rawId] = args;
if (!mode) {
log('Usage: node examples/pixmind-image.mjs submit | resume TASK_ID');
return;
}
if (!((mode === 'submit' && args.length === 1) || (mode === 'resume' && args.length === 2))) {
throw new Error('Use submit, or resume followed by a numeric taskId');
}
const resumeId = mode === 'resume' ? parseTaskId(rawId) : undefined;
const client = createClient({ ...deps, apiKey: env.PIXMIND_API_KEY });
const id = resumeId ?? await client.submit({
model: env.PIXMIND_MODEL || 'nano-banana-pro',
prompt: env.PIXMIND_PROMPT || 'A studio photograph of an unbranded ceramic coffee cup on a plain background',
});
// Save this line in your application record before relying on the polling process.
log(`TASK_ID=${id}`);
log(`Resume without a new generation: node examples/pixmind-image.mjs resume ${id}`);
const images = await client.poll(id);
log(JSON.stringify({ taskId: id, images }, null, 2));
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch(error => { console.error(error.message); process.exitCode = 1; });
}
먼저 아무것도 제출하지 않고 명령 인터페이스를 확인하세요.
node examples/pixmind-image.mjs
키, 모델 매개변수, 예상 요금을 확인한 뒤 작업 1건을 생성하세요.
node examples/pixmind-image.mjs submit
생성에 성공하면 클라이언트는 이미지를 기다리기 전에 TASK_ID를 즉시 출력합니다. 실제 값을 비공개 위치에 복사해 두세요. 이 예제는 ID를 파일이나 데이터베이스에 영구 저장하지 않습니다. 따라서 출력을 저장하지 않은 채 터미널을 닫으면 복구에 필요한 참조 정보를 잃을 수 있습니다.
작업 조회를 계속하려면 12345를 저장해 둔 값으로 바꾸세요.
node examples/pixmind-image.mjs resume 12345
이 명령은 작업 GET 요청만 수행합니다. 새 프롬프트를 읽어 다른 이미지를 생성하지 않습니다. 성공적으로 종료되면 본인 작업의 출력 URL이 표시되어야 합니다. 여기서는 조작된 프로덕션 출력을 벤치마크처럼 제공하지 않습니다.
예제는 HTTP 요청당 제한 시간을 30초, 전체 폴링 제한 시간을 5분으로 설정합니다. GET 재시도에는 무작위 지연을 더한 지수 백오프를 사용하며, 로컬 백오프의 상한은 10초입니다. 해당하는 경우 유효한 Retry-After 응답 헤더도 따릅니다. 서버가 더 긴 대기 시간을 요청하면 이를 우선하며, 그 대기 시간이 로컬 제한 시간을 초과한다면 클라이언트는 일찍 조회하지 않고 중단합니다. 일시적인 GET 오류가 5회 연속 발생하면 해당 시도를 종료하므로, 한 번의 연속 오류 구간에서는 4회 재시도할 수 있습니다. 이 값은 클라이언트 설정이며, 서비스 수준 계약이나 모든 이미지가 5분 이내에 완성된다는 약속이 아닙니다.
애플리케이션에 적합한 전체 대기 정책을 선택하세요. 오래 걸리는 작업은 터미널 세션이나 웹 요청보다 더 오래 실행될 수 있습니다. 배포된 서비스에서는 작업 ID를 자체 작업 기록과 함께 저장하고, 사용자에게 하나의 연결을 계속 열어 두도록 요구하는 대신 브라우저에 별도의 상태 조회 엔드포인트를 제공하세요.
유료 작업을 중복 생성하지 않는 오류 처리
생성 결과가 불확실한 경우와 상태 조회가 실패한 경우를 다르게 처리하세요. 작업 조회 GET은 반복해도 다른 생성 작업을 만들지 않습니다. 반면 응답을 받지 못한 생성 POST는 이미 작업을 시작했을 수 있으므로, 이 예제는 생성 요청을 자동으로 다시 보내지 않습니다.
| 관찰된 상황 | 다음 조치 |
|---|---|
HTTP 400 |
명시적으로 다시 시도하기 전에 요청 형식, 모델 ID, 지원되는 매개변수를 확인하세요. |
HTTP 401 또는 403 |
인증 및 접근 권한을 확인하세요. 디버깅 중에 키를 로그에 남기지 마세요. |
작업 조회에서 HTTP 404 |
저장한 숫자형 ID와 작업 소유 계정을 확인하세요. 다른 사람의 작업 ID로 바꾸지 마세요. |
작업 GET에서 HTTP 429 |
유효한 Retry-After 헤더와 클라이언트의 제한된 재시도 정책에 따라 기다리세요. |
| 작업 GET에서 네트워크 오류 또는 일시적인 서버 오류 | 백오프를 적용해 같은 조회를 재시도하고, 설정된 한도에 도달하면 중단하세요. |
작업 상태 failed |
조회를 중단하고 작업 실패를 보고하세요. 새 생성 작업을 시작하려면 별도로 결정해야 합니다. |
생성 시간 초과, 잘못된 JSON 또는 taskId 누락 |
생성 결과를 알 수 없는 상태로 처리하세요. 다시 제출하기 전에 작업 기록을 확인하세요. |
모든 오류가 하나의 응답 형식을 공유한다고 가정하지 마세요. 9월 4일 프로덕션 확인에서 인증 없이 보낸 GET /models는 HTTP 401과 함께 code, message만 반환했습니다. 반면 인증된 성공 미디어 응답에는 data, timestamp도 포함되어 있었습니다. 따라서 이 튜토리얼은 모든 응답에 requestId, retryable 플래그, 가격 필드가 있다고 보장하지 않습니다. 위의 다른 오류 사례는 클라이언트 처리 방식을 설명하며, 각 사례를 프로덕션에서 재현했다는 의미는 아닙니다. HTTP 상태와 민감 정보를 제거한 애플리케이션 메시지가 있다면 이를 보관하세요. 잘못된 형식의 본문은 오류로 처리하고, JSON.parse 내부에서 프로그램이 예기치 않게 종료되지 않도록 하세요.
폴링 시간 초과는 클라이언트가 기다리기를 멈췄다는 의미이지, 원격 작업이 취소되었다는 뜻은 아닙니다. ID를 보관하고 나중에 재개하세요. 알 수 없는 상태도 성공의 근거가 되지 않습니다. 이 클라이언트는 중단한 뒤 해당 상태를 보고하여 점검할 수 있게 합니다. switch 문에 처리할 값이 없다는 이유로 “완료” 상태로 넘어가서는 안 됩니다.
문제 해결을 위해 작업 ID, 모델 ID, 수행한 작업, HTTP 상태, 대략적인 시각을 보관하세요. 인증 헤더나 비공개 프롬프트가 포함된 전체 요청 본문은 기록하지 마세요. 진단용 응답 본문이나 출력 URL을 공유하기 전에 민감한 정보가 포함되어 있는지도 확인하세요.
동영상 및 채팅에 맞게 패턴 확장하기
동영상에도 동일한 제출 후 폴링 방식을 적용할 수 있지만, 별도의 요청 검증과 결과 처리 로직이 필요합니다. 이미지 요청 본문의 필드를 그대로 재사용할 수 있다고 가정하지 말고, 선택한 동영상 모델의 필수 입력 미디어, 길이, 해상도, 오디오 옵션을 확인하세요. 완성된 동영상은 data.images가 아니라 data.videoUrl을 사용합니다.
Video Agent 가이드는 대화형 창작 워크플로를 설명합니다. 생성 과정을 자동화하기 전에 원하는 장면을 구체화하는 데 도움이 될 수 있습니다. 다만 Agent에 특정 기능이 있다는 사실만으로 동일한 이름의 API 매개변수가 존재한다고 볼 수는 없습니다.
채팅은 별도의 연동 경로입니다. 문서에 명시된 /chat/completions 경로는 OpenAI 호환 방식이며 채팅 응답을 반환합니다. 스트리밍을 요청하면 스트림을 반환합니다. 이 응답을 이미지 클라이언트의 data.taskId 파서에 전달하지 마세요. 기본 URL이 같다고 해서 모든 엔드포인트의 응답 구조가 같은 것은 아닙니다.
이미지 처리 경로를 검증하기 전까지는 이미지 클라이언트의 범위를 좁게 유지하세요. 동영상 어댑터나 채팅 클라이언트는 각 응답 형식에 대한 테스트와 함께 별도로 추가하세요. 이렇게 분리하면 반환된 객체가 이미지 작업인지, 동영상 작업인지, 채팅 메시지인지 추측하는 하나의 함수보다 동작을 이해하고 검토하기 쉽습니다.
연동을 검증하고 다음 단계 선택하기
로컬 모의 테스트는 API 잔액을 사용하지 않고 클라이언트 동작을 확인합니다. 함께 제공되는 테스트 모음은 앞서 명시한 Windows 및 Node.js 환경에서 29개 테스트를 통과했습니다. 여기에는 POST를 한 번만 보내는 제출 경로, GET만 사용하는 재개, 잘못된 형식의 응답, 종료 상태인 실패, 제한 시간, 재시도 한도가 포함됩니다. 함께 제공되는 테스트 파일을 클라이언트 소스 옆에 저장한 다음 튜토리얼 폴더에서 테스트 모음을 실행하세요.
node --test examples/pixmind-image.test.mjs
모의 테스트로 프로덕션 이용 가능 여부나 과금을 확인할 수는 없습니다. 2026년 9월 4일에 별도로 진행한 통제된 확인에서는 동일한 클라이언트를 사용했고, 다음 범위를 기록했습니다.
| 테스트 항목 | 관찰된 결과 |
|---|---|
| 요청 | nano-banana-pro, type: "image", aspectRatio: "1:1", resolution: "1K"를 사용한 POST /generations 1회 |
| 프롬프트 | A studio photograph of an unbranded ceramic coffee cup on a plain background |
| 복구 참조 정보 | 폴링 전에 보관한 숫자형 작업 ID 64114. 이는 증거 참조용이며, 독자가 조회할 ID가 아닙니다. |
| 완료 | 동일한 작업에 GET 조회 8회 수행. 최종 상태는 ready였으며 data.images에 URL 1개가 포함되어 있었습니다. |
| 출력 확인 | 반환된 이미지는 1024 × 1024픽셀로 열렸으며, 단색 배경 위에 도자기 컵이 보였습니다. |
| 과금 | API 가격 응답은 이 설정에 대해 120포인트를 제시했고, 작업에 연결된 API 거래 내역에는 120포인트 차감이 기록되어 있었습니다. |
이 확인에는 기존 프로젝트 API 키 1개와 민감하지 않은 프롬프트를 사용했으며, 참조 파일을 업로드하거나 생성 요청을 자동 재시도하지 않았습니다. 거래 내역은 Studio 잔액 표시를 보고 추정한 것이 아니라 작업 ID와 대조했습니다. 실제 API로 테스트한 범위는 이 설정 한 가지와 이미지 생성 성공 경로뿐입니다. 동영상, 채팅, 실패 시 과금, 환불, 반복 실행의 신뢰성은 테스트하지 않았습니다. 이 과거 청구액을 상시 견적으로 받아들이지 말고, 직접 제출하기 전에 가격을 다시 확인하세요.
예제를 실제 사용자 대상 워크플로에 연결하기 전에 다음을 확인하세요.
- 키가 서버에만 보관되고 클라이언트 번들, 스크린샷, 공개 로그에는 포함되지 않습니다.
- 선택한 모델과 모든 매개변수가 현재 API에서 지원됩니다.
- 제출이 접수되면 복구에 사용할 수 있는 숫자형 작업 ID가 남습니다.
- 일시적인 조회 오류가 발생한 뒤에도
resume은 생성 POST를 보내지 않습니다. - 작업 실패, 빈 결과, 로컬 시간 초과를 각각 다른 진단 결과로 구분합니다.
- 생성 응답을 받지 못해도 두 번째 제출이 자동으로 발생하지 않습니다.
직접 통제된 실제 API 확인을 진행한다면 계정, 모델, 승인된 비용 범위, 민감 정보를 제거한 요청, 작업 ID, 종료 상태의 응답, 실제 과금 내역을 기록하세요. URL의 존재 여부를 확인하는 것과 별개로 출력도 직접 살펴보세요. 새 제출마다 유료 작업이 생성될 수 있다는 점을 고려하지 않은 채 “스모크 테스트”를 반복하지 마세요.
준비가 되면 API 키를 생성하고 제출 방법을 하나 선택하세요. 큐, 업로드, 일괄 처리를 추가하기 전에 저장한 작업을 조회하는 워크플로부터 구축하세요. 다음 단계가 생성된 에셋을 창작 검토 과정에 연결하는 것이라면, Canvas 제품 광고 워크플로에서 사람이 주도하는 별도의 예시를 확인할 수 있습니다. 이는 Canvas 프로젝트를 이 API로 실행할 수 있다는 약속이 아닙니다.
편집 책임: 이 튜토리얼의 조직 명의 작성자는 PixMind Editorial Team입니다. 근거 자료는 공식 문서, 프로젝트의 미디어 컨트롤러 구현, 로컬 모의 테스트 29개, 위에서 설명한 프로덕션 확인 1건이며, 모두 2026년 9월 4일에 검토했습니다. 민감 정보를 제거한 작업 및 과금 기록은 편집 검증을 위해 보관하고 있습니다. 개별 엔지니어의 자격이나 경력, 모델 품질 비교 결과, 성능 벤치마크를 주장하지 않습니다.


