
게임 AI 이미지 캐싱 가이드: 비용 절감 방법
게임 이미지 생성 비용을 절감하기 위해 의미론적 키, 단일 제출, 비동기 폴링, 예산 제한, 영구 저장소를 활용한 캐시 우선 파이프라인을 구축합니다.
캐싱은 플레이어당 AI 이미지 API 비용을 평균 $0.002까지 내릴 수 있지만, 완전히 새로운 이미지를 더 싸게 만들 수는 없습니다. Z-Image의 현재 reAPI 요금인 완료당 $0.005에서 제어하는 규칙은 다음과 같습니다: 유료 미스율 × 수락된 자산당 완료된 시도 횟수 ≤ 0.4. 40% 미스율은 모든 새 자산이 첫 번째 완료된 시도에서 통과할 때만 한계에 도달합니다; $0.002 이하를 유지하려면 이 경계선보다 낮은 결과가 필요합니다.[1]
항목의 의미를 캐시하세요. 프롬프트만 캐싱하지 말고요. 동시에 발생한 미스를 병합하고, 가능성 있는 항목만 사전 생성하며, API 응답당이 아닌 게임 자산당 수락된 비용을 측정합니다.
TL;DR
- 완료당 Z-Image 생성 $0.005에서 크래프트당 $0.002 이하의 유효 비용을 목표로 하는 게임은
유료 미스율 × 수락된 자산당 완료된 시도 횟수 ≤ 0.4가 필요합니다. 정확히 한 번의 시도로 40%가 한계에 도달합니다; 엄격히 그 이하는 40% 미만을 의미합니다.[1] - 플레이어가 조합한 것을 위한 recipe key와 결과 객체 및 아트 버전을 위한 semantic asset key를 유지하세요.
- 공유 데이터베이스 고유성 제약 조건으로 활성 자산 작업 하나를 적용하세요. 메모리 내 Promise 맵은 같은 프로세스 호출자를 병합할 수 있지만 여러 워커를 보호하는 락은 아닙니다.
- 이미지 생성은 비동기입니다. 한 번 제출하고, 작업 ID를 유지하며, 작업 엔드포인트를 폴링하고, 완료된 출력을 제어하는 스토리지에 복사합니다. [2][3]
- 3개 작업 스모크 테스트는 15 credits 또는 $0.015 총액에서 첫 번째 제출에서 3개의 원본 후보를 완료했습니다. 시각적으로 검토되지 않았으므로 수락된 자산이 아닌 후보입니다.[4]
- 캐싱 후 이미 3,000개의 일일 이미지가 진정으로 새로운 의미론적 자산인 경우, 기존 캐시는 경제성을 구하지 못합니다. 게임은 더 많은 아트를 재사용하거나, 생성 표면을 좁히거나, 더 높은 비용을 지원해야 합니다.
실제 문제는 크래프트당 비용이지, 이미지당 비용이 아닙니다
철과 불 같은 아이템을 조합한 뒤 새로운 결과와 이미지를 함께 생성하는 무한 조합 게임을 가정해 보겠습니다. 플레이어가 하루 3,000번 조합하고 그중 상당수가 이전에 없던 조합이라면, 미디어 비용을 조합당 $0.002 미만으로 유지하려면 어떻게 해야 할까요? 핵심 제약은 분명합니다. 게임이 수익으로 감당할 수 있는 속도보다 더 빠르게 새 요청을 만들 수 있습니다.
다음 식에서 시작합니다:
daily generation cost =
craft events
x paid miss rate
x completed attempts per accepted asset
x price per completed generation완료당 Z-Image 작업 $0.005에서 한계는:
$0.005 x paid miss rate x completed attempts per accepted asset <= $0.002
paid miss rate x completed attempts per accepted asset <= 0.430일 동안 하루 3,000건의 크래프트 이벤트에서 한 번의 시도 시나리오는 다음과 같습니다:
| 유료 미스율 | 일일 새 생성 | 30일 API 비용 |
|---|---|---|
| 100% | 3,000 | $450 |
| 40% | 1,200 | $180 |
| 20% | 600 | $90 |
| 10% | 300 | $45 |
이것은 시나리오이지 예측된 히트율이 아니며, 모든 행은 수락된 자산당 한 번의 완료된 시도를 가정합니다. 예를 들어 1.5 시도에서 미스율은 같은 $0.002 한계를 유지하기 위해 26.7% 이하여야 합니다. 3,000개의 모든 이벤트가 의미론적 중복 제거 후에도 고유하다면 첫 번째 행이 적용됩니다.
Recipe keys와 semantic asset keys가 다른 문제를 해결합니다
Recipe key는 플레이어의 입력을 나타냅니다. 순서가 규칙에서 중요하지 않으면 해시하기 전에 정확한 정규 항목 ID를 정렬합니다. 해시되지 않은 신원은 쉽게 검사할 수 있습니다:
recipe identity: v3 | fire | iron이는 iron + fire과 fire + iron이 동일한 서버 소유 recipe 행을 통해 해결되도록 합니다. 나중의 밸런스 패치가 결과를 변경할 수 있으므로 규칙 버전을 포함합니다. 키에 대해 ID를 slugify하지 마세요: 손실 정규화는 두 개의 다른 항목을 병합할 수 있습니다.
Semantic asset key는 게임이 최종적으로 표시하는 것을 나타냅니다. 해시되지 않은 신원은 마찬가지로 명확할 수 있습니다:
asset identity: ember-shield | inventory-icon-v1 | z-image | 1:1 | filtered여러 레시피가 ember-shield로 해결될 수 있습니다; 번역도 이를 다르게 이름 지을 수 있습니다. 그 경로들은 여전히 하나의 승인된 비주얼을 재사용할 수 있습니다.
원본 프롬프트는 좋지 않은 키를 만듭니다: 단어 변경은 캐시를 조각내고, 동일한 프롬프트는 아트 방향 변경 후에 구식이 됩니다. 각 레시피를 안정적인 결과 ID에 매핑하고, 결과 ID, 모델, 종횡비, 안전 모드 및 스타일 버전으로 아트를 키합니다. 새로운 아트를 원할 때 버전을 범프합니다.
캐시 우선 요청 흐름
생성을 배경 자산 생성으로 취급합니다. 플레이어가 시작할 때도 마찬가지입니다. 실용적인 흐름은:
- 들어오는 recipe를 의미론적 결과 ID로 해결합니다.
- 내구성 있는 스토리지에서 의미론적 자산 키를 찾습니다.
- 히트에서 저장된 URL을 즉시 반환합니다.
- 미스에서 의미론적 자산으로 키된 공유 작업 행을 원자적으로 삽입합니다.
- 같은 데이터베이스 트랜잭션에서 공유 일일 제출 예산에서 한 슬롯을 예약합니다. 다른 워커는 기존 작업을 반환합니다.
- 행을
submitting으로 커밋하고 한 번의 POST를 만들기 전에, 응답 직후 즉시 작업 ID를 유지합니다. - 제출이 불명확한 경우
submit_uncertain으로 표시하고 중지합니다. 알려진 작업 ID를completed또는failed가 될 때까지 GET으로 폴링합니다. - 터미널 상태와 확정된 credits을 기록합니다. 완료된 이미지를 자신의 스토리지에 복사하고
pending_review로 이동합니다. - 수락 확인만
pending_review를ready로 이동할 수 있습니다.
6단계 후 플레이스홀더와 pending 상태를 반환합니다. 워커가 폴링하도록 하고 클라이언트는 게임 엔드포인트를 확인합니다. reAPI 키를 클라이언트에 노출하지 마세요.
폴링은 무료이고 중심 작업 엔드포인트는 5초 동안 캐시되므로 약 5초마다 폴링합니다.[3] 429에서 Retry-After를 존경합니다. POST를 맹목적으로 재시도하지 마세요: reAPI는 Idempotency-Key로 생성 요청을 중복 제거하지 않으므로 손실된 응답은 수락된 작업을 숨기고 두 번째 요금을 매길 수 있습니다.[5]
공유 락이 있는 실행 가능한 Node 20 모듈
여러 워커를 보호하는 가장 작은 예제는 공유 상태가 필요합니다. 아래 모듈은 순수 .mjs이고, postgres 패키지를 통해 PostgreSQL을 사용하며, 제출 후 반환합니다. 플레이어의 요청을 열어 유지하는 대신입니다.
npm install postgres테이블을 한 번 만듭니다. 게임의 자신의 규칙 데이터로 game_recipes를 채웁니다; 브라우저는 결과 ID 또는 디스플레이 이름이 아닌 두 항목 ID를 보냅니다.
CREATE TABLE game_recipes (
recipe_key text PRIMARY KEY,
result_item_id text NOT NULL,
result_display_name text NOT NULL
);
CREATE TABLE game_asset_jobs (
asset_key text PRIMARY KEY,
origin_recipe_key text NOT NULL REFERENCES game_recipes(recipe_key),
result_item_id text NOT NULL,
state text NOT NULL CHECK (state IN (
'submitting', 'submit_uncertain', 'processing', 'completed',
'pending_review', 'ready', 'failed', 'rejected'
)),
submit_owner uuid NOT NULL,
prompt text NOT NULL,
task_id text UNIQUE,
source_url text,
durable_url text,
usage_credits integer,
error_message text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE game_generation_budgets (
budget_day date PRIMARY KEY,
submissions integer NOT NULL CHECK (submissions >= 0),
submission_limit integer NOT NULL CHECK (submission_limit > 0)
);이를 cache-first.mjs로 저장하세요. 전체 SHA-256 키는 정확한 정규 ID를 해시합니다; 두 개의 다른 ID를 축소할 수 있는 slugification 단계는 없습니다.
import { createHash, randomUUID } from 'node:crypto';
import postgres from 'postgres';
const API_BASE = 'https://reapi.ai/api/v1';
const apiKey = process.env.REAPI_API_KEY;
const databaseUrl = process.env.DATABASE_URL;
const dailyLimit = Number(process.env.DAILY_NEW_ASSET_LIMIT ?? '100');
if (!apiKey) throw new Error('Set REAPI_API_KEY on the server');
if (!databaseUrl) throw new Error('Set DATABASE_URL on the server');
if (!Number.isInteger(dailyLimit) || dailyLimit < 1) {
throw new Error('DAILY_NEW_ASSET_LIMIT must be a positive integer');
}
const sql = postgres(databaseUrl);
const localFlights = new Map(); // L1 only; PostgreSQL owns correctness.
function hashIdentity(value) {
return createHash('sha256')
.update(JSON.stringify(value), 'utf8')
.digest('hex');
}
function canonicalId(value) {
if (typeof value !== 'string' || value.length === 0 || value.length > 200) {
throw new Error('Item IDs must be non-empty canonical server IDs');
}
return value; // Preserve the exact string; do not trim, slugify, or case-fold.
}
export function recipeKey(leftItemId, rightItemId) {
const pair = [canonicalId(leftItemId), canonicalId(rightItemId)].sort(
(a, b) => (a < b ? -1 : a > b ? 1 : 0),
);
return `recipe:v3:${hashIdentity({ rulesVersion: 'v3', itemIds: pair })}`;
}
function assetKey(resultItemId) {
return `asset:${hashIdentity({
resultItemId,
styleVersion: 'inventory-icon-v1',
model: 'z-image',
aspectRatio: '1:1',
contentFilter: true,
})}`;
}
async function resolveRecipe(leftItemId, rightItemId) {
const key = recipeKey(leftItemId, rightItemId);
const [recipe] = await sql`
SELECT result_item_id, result_display_name
FROM game_recipes
WHERE recipe_key = ${key}
`;
if (!recipe) throw new Error('Unknown recipe');
if (recipe.result_display_name.length > 200) {
throw new Error('Canonical display name is too long');
}
return { ...recipe, recipeKey: key };
}
function publicJob(job) {
return {
assetKey: job.asset_key,
state: job.state,
taskId: job.task_id ?? null,
url: job.state === 'ready' ? job.durable_url : null,
usageCredits: job.usage_credits ?? null,
};
}
async function claimSharedJob(recipe, key, prompt) {
const owner = randomUUID();
return sql.begin(async (tx) => {
const created = await tx`
INSERT INTO game_asset_jobs (
asset_key, origin_recipe_key, result_item_id,
state, submit_owner, prompt
) VALUES (
${key}, ${recipe.recipeKey}, ${recipe.result_item_id},
'submitting', ${owner}, ${prompt}
)
ON CONFLICT (asset_key) DO NOTHING
RETURNING *
`;
if (created.length === 0) {
const [existing] = await tx`
SELECT * FROM game_asset_jobs WHERE asset_key = ${key}
`;
if (!existing) throw new Error('Concurrent job was not readable');
return { ownsSubmit: false, job: existing };
}
const budget = await tx`
INSERT INTO game_generation_budgets (
budget_day, submissions, submission_limit
) VALUES (
(now() AT TIME ZONE 'UTC')::date, 1, ${dailyLimit}
)
ON CONFLICT (budget_day) DO UPDATE SET
submissions = game_generation_budgets.submissions + 1,
submission_limit = EXCLUDED.submission_limit
WHERE game_generation_budgets.submissions < EXCLUDED.submission_limit
RETURNING submissions
`;
if (budget.length === 0) throw new Error('Shared daily budget reached');
return { ownsSubmit: true, owner, job: created[0] };
});
}
async function markSubmitUncertain(key, owner, message) {
const [job] = await sql`
UPDATE game_asset_jobs
SET state = 'submit_uncertain', error_message = ${message},
updated_at = now()
WHERE asset_key = ${key} AND submit_owner = ${owner}
AND state = 'submitting'
RETURNING *
`;
return job;
}
async function submitClaimedJob(claim, key, prompt) {
let response;
let body = '';
try {
response = await fetch(`${API_BASE}/images/generations`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'z-image',
prompt,
aspect_ratio: '1:1',
content_filter: true,
}),
});
body = await response.text();
} catch (error) {
const job = await markSubmitUncertain(
key,
claim.owner,
error instanceof Error ? error.message : String(error),
);
return publicJob(job ?? claim.job);
}
let task;
try {
task = JSON.parse(body);
} catch {
task = null;
}
if (!response.ok || !task?.id) {
const message = `Unconfirmed submit response: HTTP ${response.status}`;
const job = await markSubmitUncertain(key, claim.owner, message);
return publicJob(job ?? claim.job);
}
const [saved] = await sql`
UPDATE game_asset_jobs
SET state = 'processing', task_id = ${task.id}, updated_at = now()
WHERE asset_key = ${key} AND submit_owner = ${claim.owner}
AND state = 'submitting'
RETURNING *
`;
if (!saved) {
throw new Error(`Task ${task.id} was accepted but its ID was not persisted; do not resubmit`);
}
return publicJob(saved);
}
async function startOrRead(recipe, key) {
const prompt = [
`A clean 3D inventory icon of ${recipe.result_display_name}.`,
'One centered object, readable silhouette, plain warm background,',
'soft studio light, no text, no logo, no existing game character.',
].join(' ');
const claim = await claimSharedJob(recipe, key, prompt);
if (!claim.ownsSubmit) return publicJob(claim.job);
return submitClaimedJob(claim, key, prompt);
}
export async function getOrCreateCraftAsset({ leftItemId, rightItemId }) {
const recipe = await resolveRecipe(leftItemId, rightItemId);
const key = assetKey(recipe.result_item_id);
const existing = localFlights.get(key);
if (existing) return existing;
const work = startOrRead(recipe, key).finally(() => {
if (localFlights.get(key) === work) localFlights.delete(key);
});
localFlights.set(key, work);
return work;
}
export async function pollAsset(key) {
const [job] = await sql`
SELECT * FROM game_asset_jobs WHERE asset_key = ${key}
`;
if (!job) throw new Error('Unknown asset job');
if (job.state !== 'processing' || !job.task_id) return publicJob(job);
const response = await fetch(`${API_BASE}/tasks/${job.task_id}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
if (response.status === 429) {
const parsed = Number.parseInt(response.headers.get('retry-after') ?? '', 10);
return {
...publicJob(job),
retryAfterSeconds: Number.isFinite(parsed) ? Math.max(parsed, 1) : 5,
};
}
if (!response.ok) {
throw new Error(`GET task failed with HTTP ${response.status}; retry this GET, not the POST`);
}
const task = await response.json();
if (task.status === 'processing') return publicJob(job);
const credits = Number.isInteger(task.usage?.credits)
? task.usage.credits
: null;
if (task.status === 'failed') {
const [failed] = await sql`
UPDATE game_asset_jobs
SET state = 'failed', usage_credits = ${credits},
error_message = ${task.error?.message ?? 'Generation failed'},
updated_at = now()
WHERE asset_key = ${key} AND task_id = ${job.task_id}
RETURNING *
`;
return publicJob(failed);
}
if (task.status !== 'completed') {
throw new Error(`Unexpected task status: ${task.status}`);
}
const sourceUrl = task.output?.image_urls?.[0] ?? null;
const [completed] = await sql`
UPDATE game_asset_jobs
SET state = 'completed', source_url = ${sourceUrl},
usage_credits = ${credits},
error_message = ${sourceUrl ? null : 'Completed task returned no image URL'},
updated_at = now()
WHERE asset_key = ${key} AND task_id = ${job.task_id}
RETURNING *
`;
return publicJob(completed);
}
export async function stageForReview(key, durableUrl) {
const [job] = await sql`
UPDATE game_asset_jobs
SET state = 'pending_review', durable_url = ${durableUrl},
updated_at = now()
WHERE asset_key = ${key} AND state = 'completed'
AND source_url IS NOT NULL
RETURNING *
`;
if (!job) throw new Error('Only an archived completed asset can enter review');
return publicJob(job);
}
export async function reviewAsset(key, accepted, reason = null) {
const nextState = accepted ? 'ready' : 'rejected';
const [job] = await sql`
UPDATE game_asset_jobs
SET state = ${nextState}, error_message = ${reason}, updated_at = now()
WHERE asset_key = ${key} AND state = 'pending_review'
RETURNING *
`;
if (!job) throw new Error('Asset is not pending review');
return publicJob(job);
}데이터베이스 행(not localFlights)은 두 번째 워커가 동일한 자산을 제출하는 것을 방지합니다. POST 중에 죽는 프로세스는 submitting을 남기고; 오래된 행을 submit_uncertain으로 취급하고 그것을 재활용하는 대신 조사합니다. 알려진 작업 ID가 존재한 후 일시적인 폴링 실패는 GET만을 재시도합니다.
공유 카운터는 보수적인 제출 상한입니다. 확인되지 않거나 거부된 POST는 여전히 그 슬롯을 소비합니다. 이는 조용히 상한을 초과하는 것보다 더 안전합니다. 터미널 작업이 실패하면 그 상태와 usage.credits는 행에 남습니다. 이 예는 실패하거나 거부된 자산을 자동 재시도하지 않습니다; 재시도를 지원하기 전에 별도의 시도 원장과 명시적인 예산 운영자 작업을 추가합니다.
스토리지 워커는 source_url을 다운로드하고, 객체 저장소에 업로드하고, 그 내구성 있는 URL을 stageForReview로 전달해야 합니다. reviewAsset(..., true)만 캐시 가능한 ready 자산을 생성합니다.
3개 작업 스모크 테스트가 증명하는 것
2026년 9월 3일, 우리는 content_filter: true로 3개의 원본 1:1 인벤토리 항목 프롬프트를 제출했습니다: 이끼 등불, 유리 나침반, 그리고 연소 방패. 각 작업은 첫 번째 제출에서 완료되었고 5 credits를 보고했습니다.
[4]
| 후보 | 작업 ID | 관찰된 완료 시간 | Credits |
|---|---|---|---|
| Moss lantern | task_01a065490250739d893a1efcd83f4d1d | 25.1 s | 5 |
| Glass compass | task_01a0654995df7265b44b132dce34a914 | 25.0 s | 5 |
| Ember shield | task_01a0654995fd73e28e7c682170664070 | 18.2 s | 5 |
| 합계 | 3 tasks | — | 15 / $0.015 |
공개 스모크 테스트 기록은 요청 설정, 작업 ID, 경과 시간 및 확정된 credits을 포함합니다. 임시 출력 URL은 의도적으로 생략합니다.[4]
이것은 스모크 테스트이지 지연 시간 벤치마크가 아닙니다; p95, 버스트 또는 장기 신뢰성에 대해 아무것도 말하지 않습니다. 프로토콜은 작업 결과와 정착을 기록했지만 아트 검토를 포함하지 않았습니다. completed는 3개의 후보가 도착했다는 의미이지, 아트 검토자가 수락했다는 의미가 아닙니다.
이것이 유용한 창조적 메트릭이 이유입니다:
cost per accepted asset =
total settled generation credits x $0.001 / accepted assets3번의 시도가 1개의 사용 가능한 아이콘을 산출하면 효과적인 비용은 $0.015입니다. 기술 상태와 창조적 수락을 별도로 기록합니다.
상상의 절감이 아닌 지연 시간을 위해 사전 생성하세요
플레이어의 인벤토리에서 도달 가능한 상위 20개 레시피와 같은 예측 가능한 요청만 사전 생성합니다. 동일한 의미론적 캐시와 단일 비행 경로를 사용합니다. 별도의 일일 상한선입니다.
이론적 조합 공간을 생성하지 마세요. 사용하지 않은 자산은 돈이고, 개방 조합은 부족 큐를 초과 성장합니다. 관찰된 수요로 순위를 매기고 상한선에서 중지합니다.
하이브리드는 보통 최고로 느껴집니다:
- 승인된 의미론적 자산은 즉시 반환됩니다;
- 높은 확률의 다음 항목이 사전 생성됩니다;
- 긴 꼬리 미스는 하나의 배경 작업이 실행되는 동안 플레이스홀더를 표시합니다;
- 인기 없거나 학대적인 요청은 무제한 재시도 루프 대신 할당량에 도달합니다.
안전성, 저장소 및 운영 경계
플레이어가 직면한 생성을 위해 content_filter: true를 유지합니다. Z-Image는 그 설정을 기본적으로 끕니다. 따라서 기본값에 의존하는 것은 충분하지 않습니다.
[2] 플레이어 텍스트를 프롬프트가 되기 전에 중재하거나 제약하고, 보호된 캐릭터 또는 실제 사람을 모방하려는 시도를 차단하고, 계정 및 장치별로 속도 제한합니다. 업스트림 필터는 한 층이지 완전한 게임 정책이 아닙니다.
키를 서버 측에 유지하세요. Recipe 및 자산 키, 작업 ID, 상태, credits 및 수락을 불필요한 플레이어 데이터를 유지하지 않고 로깅합니다.
마지막으로 완료된 파일을 게임 클라이언트에 해당 URL을 게시하기 전에 내구성 있는 저장소에 복사합니다. 작업 출력 URL은 영구적 자산 계약이 아닙니다. [3] 자산 레코드 옆에 체크섬 및 콘텐츠 유형을 저장하십시오. 그래야 재시도가 몰래 승인된 아트를 대체할 수 없습니다.
결론: 캐싱이 충분하지 않을 때
캐싱은 게임이 재사용을 허용할 때만 작동합니다. 유료 미스율 × 수락된 자산당 완료된 시도 횟수이 0.4를 초과하면 $0.005 단가에서 $0.002 상한선을 유지할 수 없습니다. 원본 예의 3,000개 일일 요청이 캐싱 후 이미 고유한 의미론적 자산인 경우 캐시 소프트웨어를 변경해도 문제를 해결하지 못합니다.
그러면 제품 제약을 변경하세요: 비슷한 항목 간 아트 공유, 절차 재색칠 또는 오버레이 사용, 인기 있는 항목만 생성, 발견 제한, 긴 꼬리 생성 요금 부과, 또는 더 큰 미디어 예산 수용합니다.
예산을 설정하기 전에 Z-Image 모델 페이지에서 라이브를 확인하고, Z-Image API 가이드 및 Tasks API 참고를 사용하여 제출 및 폴링을 구현합니다. 가격 및 모델 동작은 변경될 수 있습니다; 자신의 확정된 credits은 프로덕션 청구서의 진실 원천으로 남습니다.
FAQ
캐싱이 새 AI 이미지의 가격을 줄입니까?
아닙니다. 구매 빈도를 줄입니다. 현재 Z-Image 요금은 완료 생성당 5 credits입니다; 캐시 히트는 새로운 생성 credits을 소비하지 않습니다.
프롬프트 자체가 캐시 키여야 합니까?
보통 그렇지 않습니다. Recipe를 안정적인 결과 ID로 해결한 다음, 아트를 별도로 버전 지정합니다.
항목 순서가 중요하면 어떻게 됩니까?
Recipe 입력을 정렬하지 마세요. 규칙이 다른 결과를 제공할 때마다 water + fire과 fire + water는 다른 recipe 키를 가져야 합니다.
게임이 자산을 사전 생성해야 합니까? 아니면 실시간으로 생성해야 합니까?
둘 다 사용: 예산 가능한 가능성 있는 세트를 사전 생성하고, 히트를 즉시 제공하며, 배경에서 예측 불가능한 긴 꼬리 항목을 생성합니다.
reAPI 이미지 작업을 얼마나 자주 폴링해야 합니까?
약 5초마다가 실용적인 시작점입니다. 더 빠른 폴링은 생성이 더 빨리 완료되도록 하지 않으며 비율 제한 예산을 낭비할 수 있습니다.
실패한 생성이 청구됩니까?
Z-Image 설명서는 실패 및 거부된 요청이 청구되지 않는다고 말합니다.
여전히 status를 usage.credits과 함께 검사하세요: 완료되었지만 시각적으로 사용할 수 없는 결과는 실패한 작업과 다르며 창조적 비용으로 계산됩니다.[2][3]
크래프트당 $0.002 이하의 평균을 약속할 수 있습니까?
유료 미스율 및 수락된 자산당 완료된 시도를 측정한 후에만. $0.005 per image에서 엄격히 $0.002 아래에 머무르려면 그 곱이 0.4 아래에 머물러야 합니다. 40% 미스율은 모든 자산이 첫 번째 완료된 시도에서 통과할 때 단순히 상한선을 만족합니다.
게임 클라이언트가 이미지 엔드포인트를 직접 호출할 수 있습니까?
그렇지 않아야 합니다. 클라이언트 측 자격증명은 추출되어 남용될 수 있습니다. 생성을 백엔드를 통해 라우팅하고, 거기에서 할당량을 적용하고, 클라이언트가 게임 소유 보류 중인 작업을 쿼리하도록 합니다.
참고
- reAPI. Z-Image model page and live pricing. Retrieved September 3, 2026 from reapi.ai/models/z-image
- reAPI. Z-Image API documentation: inputs, asynchronous response, pricing, filters, and output retention. Retrieved September 3, 2026 from reapi.ai/docs/z-image
- reAPI. Tasks API: status, settled usage, polling, rate-limit behavior, and output storage. Retrieved September 3, 2026 from reapi.ai/docs/api/tasks
- reAPI. Three-task Z-Image smoke-test record. Run September 3, 2026. Temporary output URLs are omitted; no visual acceptance review was performed.
- reAPI. API overview: idempotency behavior. Retrieved September 3, 2026.
작성자

카테고리
더 많은 게시물

일일 AI 영상 생성 비용은 얼마? 30일 API 예산 계획
30일 수직형 AI 영상 예산을 계획하고, 재시도, 무료 크레딧 한도, 안전한 API 폴링, 스토리지, 인적 검토를 포함하는 실무 가이드입니다.


AI 뮤직비디오 생성 API: 음악·사진·가사로 전곡 영상 만들기
10초~5분 음악 파일과 1~7개 참고 이미지로 완성된 뮤직비디오를 한 번의 비동기 API 요청으로 생성하는 방법, 자막 옵션, 정확한 가격표를 정리했습니다.


Wan 2.7 영상 생성 API 가이드: 1080p, 음성, 가격 및 제한
Wan 2.7 영상 API를 텍스트, 이미지, 참조, 영상 편집 작업에 사용하세요. 1080p 가격, 음성 제어, 2~15초 제한, 코드 예시를 설명합니다.
