Documentação da API Makernice: início rápido
- Ideal para
- Desenvolvedores que chamam a geração de vídeo e imagem a partir do próprio código via REST: cotação, criação e depois consulta de status ou recebimento de webhook.
- Não indicado para
- A API da Makernice não serve para criar vídeos ou imagens sem escrever código; para isso, use a seção Criar do console, com os mesmos modelos, preços e saldo.
Gere um vídeo com uma chave de API e três requisições: peça uma cotação, crie a geração e consulte até ficar pronta.
1. Crie uma chave de API
No console, abra Desenvolvedores → Chaves de API e crie uma chave. Envie-a como token Bearer em todas as requisições. As gerações são cobradas da sua carteira; você pode limitar cada chave com um limite de gastos.
2. Peça uma cotação
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":"Travelling lento por dentro de uma estufa","duration":5,"resolution":"720p"}'
# → (valores ilustrativos) { "id": "qt_…", "amount": "500000", "expires_at": "…", "facts": { "quantity": 5, … } }Os valores são em dólares americanos, em milionésimos de dólar (micro-USD): 1,000,000 = $1.00. A cotação trava o preço por 12 minutos.
3. Crie a geração
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":"Travelling lento por dentro de uma estufa","duration":5,"resolution":"720p"}}'
# → 202 { "id": "gen_…", "status": "queued", "billing": "held", … }O valor fica reservado no seu saldo. Se a geração falhar ou você cancelá-la enquanto estiver na fila, a reserva é liberada por inteiro. Repetir a requisição com a mesma Idempotency-Key devolve a mesma geração, sem cobrar duas vezes.
4. Consulte até ficar pronta
curl https://api.makernice.com/v1/generations/gen_… \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# status: queued → generating → moderating → succeeded | failed | canceled
# em succeeded: faça GET em outputs[0].url com sua chave para receber um 302 para o arquivo5. Baixe o arquivo
curl -L -o video.mp4 https://api.makernice.com/v1/generations/gen_…/outputs/0 \
-H "Authorization: Bearer $MAKERNICE_API_KEY"Python: cotação, criação e consulta
Defina MAKERNICE_API_KEY e execute:
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: cotação, criação e consulta
Node.js 18 ou superior, sem precisar de pacotes. Salve como quickstart.mjs, defina MAKERNICE_API_KEY e execute node quickstart.mjs:
// 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);
});Para rodar várias gerações ao mesmo tempo, inicie-as em paralelo (por exemplo, com um pool de threads, asyncio com um cliente HTTP assíncrono ou Promise.all). Cada conta tem, por padrão, 2 gerações em andamento ao mesmo tempo; acima disso, a API retorna 429 concurrency_limit_reached com Retry-After, e o call() dos dois exemplos espera esse tempo. Fale com o suporte para aumentar o limite.
Autenticação
Toda requisição precisa de uma chave de API no cabeçalho Authorization, exceto GET /v1/models e GET /v1/pricing. As chaves começam com mn-, seguido de 48 letras e dígitos.
curl https://api.makernice.com/v1/generations \
-H "Authorization: Bearer $MAKERNICE_API_KEY"- A chave completa aparece uma única vez, quando você a cria. Guardamos só uma impressão digital, então uma chave perdida não pode ser recuperada: crie uma nova e exclua a antiga.
- Uma chave ausente, malformada, desconhecida, desativada, expirada ou excluída sempre recebe a mesma resposta
401 invalid_api_key. Desativar ou excluir uma chave vale a partir da próxima requisição. - Chame a API a partir do seu servidor. Ela não aceita requisições de navegador entre origens (sem CORS); nunca coloque uma chave no código front-end.
- Todas as chaves de uma conta enxergam as mesmas gerações: qualquer chave pode consultar, listar e cancelar todas as gerações da conta, inclusive as criadas no console.
- Todas as chaves de uma conta usam o mesmo saldo pré-pago. Hoje, cada conta da Makernice tem um único login; equipes compartilham um saldo por meio de várias chaves de API.
- A página Chaves de API mostra o gasto total de cada chave, e a página Uso detalha o gasto da API por chave e por modelo.
- No console, você pode definir uma data de expiração e um limite de gastos para cada chave. O gasto de uma chave é a soma do valor cobrado e do valor reservado no momento das gerações que ela criou; uma requisição de criação que ultrapasse o limite recebe
402 key_spend_limit_reached. Gerações com falha e canceladas não entram na conta.
Preços e cobrança
- A cobrança é em dólares americanos. Todos os valores são strings de inteiros em micro-USD (milionésimos de dólar):
1,000,000= $1.00. Leia-os como inteiros ou decimais, não como números de ponto flutuante. - Vídeos têm preço por segundo (a quantidade é a duração pedida; num template com clipe de vídeo de referência, inclui os segundos do clipe); imagens, por requisição (uma imagem por geração). Preço = preço unitário × quantidade.
- A cotação trava o preço por 12 minutos (
expires_at). A geração é cobrada pelo valor cotado, mesmo que os preços mudem nesse intervalo.
GET /v1/pricing lista os preços unitários da versão atual da tabela de preços. Não exige chave de API e pode ficar em cache por até 60 segundos. official_unit_price, quando presente, é o preço de tabela oficial da mesma combinação e serve só para comparação; a cobrança é sempre pelo unit_price.
{
"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" }
]
}Gerações com falha são cobradas?
Não. A reserva é liberada por completo: você nunca é cobrado por uma geração com falha. Observação: se um resultado concluído for bloqueado pela moderação por violar a política, a execução ainda é cobrada; você pode pedir revisão manual ao suporte.
Quando você cria uma geração, reservamos o valor cotado. A reserva vira cobrança quando a execução é concluída; gerações com falha e tempo esgotado a liberam por completo, e o mesmo vale para cancelamentos ainda na fila. Uma reserva liberada não é um reembolso: o dinheiro nunca saiu do seu saldo.
| O que aconteceu | billing | Cobrada? |
|---|---|---|
| A geração é concluída com sucesso | charged | Sim, o valor cotado |
A geração falha (error.code generation_failed, upstream_unavailable ou invalid_request) | released | Não |
Ela passa do tempo máximo de execução (error.code: timeout) | released | Não |
Você a cancela enquanto está em queued | released | Não |
O prompt é bloqueado na criação (422 content_rejected, reason: prompt) | — | Não; nenhuma geração é criada e nada fica reservado |
Uma imagem de entrada ou a política de segurança do provedor do modelo a rejeita (content_rejected, reason input_image ou model) | released | Não |
O resultado concluído é bloqueado pela moderação do resultado (output state: blocked) | charged | Sim; fale com o suporte para uma revisão manual |
O resultado está sendo confirmado (status: checking) | held | Só se for confirmado que foi concluído com sucesso |
Obter uma cotação
Envie a entrada da geração; a resposta informa exatamente quanto a geração vai custar. Cada cotação vale para uma geração.
| Campo | Tipo | Descrição |
|---|---|---|
model | string | Obrigatório. Um id de modelo de GET /v1/models. |
mode | string | Obrigatório. t2v, i2v, flf, r2v, t2i ou i2i; cada modelo lista os modos que aceita. |
prompt | string | Obrigatório. De 1 a 4.000 caracteres por padrão; alguns modelos aceitam menos (veja maxPromptChars em GET /v1/models). |
duration | integer | Só para vídeo. Segundos, dentro da faixa do modelo; o padrão é a duração padrão do modelo. |
resolution | string | Uma das resolutions do modelo; o padrão é defaultResolution. Não é aceito quando o modelo não lista nenhuma. |
aspect_ratio | string | Um dos aspectRatios do modo. |
image_url | string | Primeiro frame. Obrigatório para i2v e flf. |
last_image_url | string | Último frame. Obrigatório para flf. |
reference_images | string[] | Obrigatório para r2v e i2i; no máximo o maxReferences do modo. |
seed | integer | Opcional. De 0 a 2.147.483.647. A mesma seed com as mesmas configurações dá resultados mais parecidos. |
generate_audio | boolean | Opcional. Define se o vídeo ganha trilha sonora. |
prompt_expansion | string | Opcional. off, balanced ou quality: quanto o modelo pode reescrever o seu prompt. |
negative_prompt | string | Opcional. Até 2.000 caracteres descrevendo o que evitar. |
watermark | boolean | Opcional. Adiciona a marca d'água de conteúdo gerado por IA do provedor. |
Os cinco últimos campos são extras opcionais. Cada modelo aceita só alguns deles: GET /v1/models lista os disponíveis para cada modo e resolução em prices[].options, com os valores permitidos e os padrões. Um campo que nenhuma rota do modelo consegue aplicar retorna 400 invalid_request; alterar um campo opcional exige uma nova cotação, como qualquer outro campo.
As imagens são URLs https públicas (até 2.048 caracteres). Campos que o modo não usa e campos desconhecidos são recusados com 400 invalid_request; para os campos de entrada, error.details lista cada problema como { path, message } (outros erros invalid_request podem ter details em outro formato ou não ter). Uma combinação sem preço retorna 422 price_unavailable.
Cada imagem deve ser uma URL https acessível publicamente que entregue uma imagem estática JPEG, PNG ou WebP (WebP animado e GIF não são aceitos), de até 10 MB, diretamente nessa URL: redirecionamentos não são seguidos. Buscamos cada imagem uma vez e guardamos uma cópia; a moderação de conteúdo e o modelo usam essa cópia. Se uma imagem não puder ser usada, a geração falha com invalid_request, error.input indica a imagem e a reserva é liberada.
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":"Travelling lento por dentro de uma estufa","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 }
}Por quanto tempo uma cotação trava o preço?
Cada cotação trava o preço por 12 minutos, e a geração é sempre cobrada exatamente pelo valor cotado.
| Campo ou caso | O que significa |
|---|---|
expires_at | 12 minutos depois que a cotação foi criada. Crie a geração antes disso. |
amount | Preço unitário × facts.quantity (segundos no vídeo, 1 na imagem), em micro-USD. Fica reservado quando você cria a geração e é cobrado se ela for concluída com sucesso. |
price_version | A tabela de preços da qual a cotação saiu. Se uma nova tabela for publicada antes de você criar a geração, você ainda paga o valor cotado. |
| Depois de 12 minutos | Criar com a cotação retorna 410 quote_expired. Nada foi reservado; peça uma nova cotação. |
| Mudou o modelo, o modo, a resolução ou a duração | 422 quote_mismatch. Mudar só o prompt mantém a cotação. |
| Já usou a cotação | 409 quote_already_used. Cada cotação vale para uma geração. |
Criar uma geração
Crie a geração com a cotação e a mesma entrada. O cabeçalho Idempotency-Key é obrigatório (veja Idempotência).
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":"Travelling lento por dentro de uma estufa","duration":5,"resolution":"720p"}}'- Responde
202com a geração (status: queued,billing: held). Uma nova tentativa com a mesma Idempotency-Key responde200com a geração original. - Você pode alterar o prompt, mas alterar qualquer coisa que afete o preço (modelo, modo, resolução, duração) retorna
422 quote_mismatch. - Uma cotação já usada retorna
409 quote_already_used; uma expirada,410 quote_expired; e uma que não está na sua conta,404 quote_not_found. - Saldo insuficiente retorna
402 insufficient_balance; ultrapassar o limite de gastos da chave retorna402 key_spend_limit_reached. callback_urlopcional no nível superior (https, até 2048 caracteres, sem credenciais na URL, sem endereço privado ou reservado): quando a geração terminar, fazemos um POST dela para esse endereço. Ele tem prioridade sobre o endereço de webhook da sua conta. Veja Webhooks.
Consultar status
Consulte a geração até ela chegar a um status final. Consultar a cada poucos segundos não tem problema; veja o limite de leitura em Limites de taxa. Uma geração de vídeo pode levar alguns minutos, e mais ainda em clipes longos ou de alta resolução. Se uma execução passar do tempo máximo, ela falha e a reserva é liberada por completo.
| Status | Significado |
|---|---|
queued | Aguardando para começar. Ainda pode ser cancelada. |
generating | O modelo está trabalhando nela. |
moderating | A geração terminou e o valor foi cobrado; os resultados estão em verificação de segurança e outputs fica vazio até ela terminar. Continue consultando. |
succeeded | Pronto. O valor foi cobrado e os outputs estão disponíveis. |
failed | Não terminou. A reserva é liberada; error traz um código e uma mensagem. |
canceled | Cancelada enquanto estava na fila. A reserva é liberada. |
checking | Estamos confirmando o resultado. O valor continua reservado até isso ser resolvido. |
O error.code de uma geração com falha é um destes: invalid_request (o modelo recusou a requisição; ajuste o prompt ou as configurações), content_rejected (recusado pela moderação de conteúdo ou pela política de segurança do provedor do modelo; a reserva é liberada), timeout, upstream_unavailable ou generation_failed. Em content_rejected, error.reason diz o que foi recusado: prompt (o prompt), input_image (uma imagem de referência) ou model (a política de segurança do provedor do modelo). O mesmo reason aparece na resposta 422 quando um prompt enviado é bloqueado. Quando o invalid_request é causado por uma imagem de referência que não pode ser usada, error.input indica qual: image_url, last_image_url ou reference_images[0] (índice a partir de zero). Troque essa imagem e crie uma nova geração.
{
"id": "gen_3b9e…",
"type": "video",
"model": "seedance-2.5",
"mode": "t2v",
"status": "succeeded",
"prompt": "Travelling lento por dentro de uma estufa",
"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 dias depois que uma geração termina, o conteúdo da requisição é apagado conforme a nossa política de retenção de dados: prompt vira uma string vazia. Os dados de cobrança (model, params, amount, status e os horários) são mantidos.
GET /v1/generations/{id}/events retorna a linha do tempo da geração. Códigos: created, submitted, generating, delivered, failed_refunded, canceled_refunded, checking, requeued.
{
"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" }
]
}Baixar resultados
Cada item de outputs tem uma url como /v1/generations/gen_…/outputs/0, relativa a https://api.makernice.com. Faça a requisição com a sua chave de API; a resposta é um redirecionamento 302 para um link temporário do arquivo. Sempre que precisar do arquivo, chame o endpoint de novo em vez de guardar o destino do redirecionamento.
curl -L -o video.mp4 https://api.makernice.com/v1/generations/gen_…/outputs/0 \
-H "Authorization: Bearer $MAKERNICE_API_KEY"- Adicione
?download=1para receber o arquivo como anexo. Isso funciona quando o arquivo estástored; enquanto estiverpendingoudegraded, o redirecionamento vai para o arquivo original e o parâmetro não tem efeito. - O
curl -Lnão envia o seu cabeçalhoAuthorizationao host do redirecionamento, então a sua chave fica só com a gente. - Só existem resultados quando a geração está em
succeeded; caso contrário, o endpoint retorna404 output_not_found. state:pending(ainda sendo salvo; o link já funciona),stored(salvo atéexpiresAt),degraded(o salvamento não foi concluído; o link pode parar de funcionar antes — baixe logo),expired(o arquivo salvo expirou; o endpoint retorna410 output_expired),blocked(o resultado não passou na moderação de conteúdo;urlé uma string vazia — peça revisão manual ao suporte).
Cancelar
Só é possível cancelar gerações em queued; a reserva é liberada por inteiro e a resposta é a geração cancelada. Depois que a geração entra em generating, ela não pode ser interrompida, e o cancelamento retorna 409 not_cancelable.
curl -X POST https://api.makernice.com/v1/generations/gen_…/cancel \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# → { "id": "gen_…", "status": "canceled", "billing": "released", … }Listar gerações
Lista as gerações da sua conta, das mais recentes para as mais antigas — inclusive as criadas no console.
| Query | Descrição |
|---|---|
limit | Tamanho da página, de 1 a 100. Padrão 20. |
cursor | O next_cursor da página anterior. |
status | Um ou mais status, separados por vírgula, por exemplo queued,generating. |
type | video ou image. |
model | Um id de modelo. |
q | Parte de um id de geração ou um texto contido no prompt (ambos sem diferenciar maiúsculas e minúsculas). |
curl "https://api.makernice.com/v1/generations?status=queued,generating&limit=50" \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# → { "data": [ …gerações… ], "next_cursor": "gen_…" | null, "total": 12 }total conta todas as gerações que correspondem aos filtros. Quando next_cursor é null, você chegou à última página.
Webhooks
Em vez de consultar, você pode pedir que a gente faça um POST da geração para o seu servidor quando ela chegar a um status final. Enviamos para a callback_url informada ao criar a geração; se não houver, para o endereço de webhook da sua conta (defina nas configurações do console). Sem nenhum dos dois, nada é enviado.
| Evento | Enviado quando |
|---|---|
generation.succeeded | A geração foi concluída com sucesso e cobrada. |
generation.failed | A geração falhou; o valor reservado foi liberado. |
generation.canceled | A geração foi cancelada; o valor reservado foi liberado. |
Requisição
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é o mesmo objeto de geração deGET /v1/generations/{id}, exceto queoutputs[].urlé uma URL absoluta.Makernice-Event-Idé igual aoiddo corpo e não muda entre as tentativas. Use-o para ignorar duplicatas.
Verifique a assinatura
Makernice-Signature tem o formato t=<segundos unix>,v1=<hex>, em que v1 é o HMAC-SHA256, com o seu segredo de assinatura, de t + "." + corpo bruto. Calcule sobre os bytes brutos que você recebeu, compare em tempo constante e recuse requisições cujo t esteja a mais de 5 minutos do seu relógio. O segredo de assinatura fica nas configurações do console; as entregas para callback_url são assinadas com o mesmo segredo. Depois que você o trocar, as entregas ainda não enviadas são assinadas com o novo.
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);
}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", ""))Entrega e novas tentativas
- Só HTTPS. Redirecionamentos não são seguidos. Cada tentativa expira após 10 segundos.
- Qualquer resposta 2xx conta como entregue. Responda rápido e faça o trabalho pesado depois.
- Caso contrário, tentamos de novo após 1 min, 5 min, 30 min, 2 h, 6 h, 12 h — 7 tentativas no total — e então marcamos a entrega como falha. As entregas recentes e a última resposta de cada uma aparecem nas configurações do console.
- Normalmente cada geração envia um evento, e uma entrega pode chegar mais de uma vez (por exemplo, depois de um timeout do seu lado). Se um resultado retido pela revisão de conteúdo for liberado depois, enviamos a geração de novo com um novo id de evento, então você pode receber dois eventos para ela; vale o mais recente.
Modelos
GET /v1/models lista os modelos e os modos, resoluções e durações que você pode usar. Só aparecem os modos que têm preço, com os preços em modes[].prices. Não exige chave de API e pode ficar em cache por até 60 segundos.
resolutions/defaultResolution: valores permitidos pararesolution. Uma lista vazia significa que o modelo tem resolução fixa e você deve omitirresolution(enviá-lo, mesmo com o valor fixo, retorna400 invalid_request). A faixa fixa é a que aparece emmodes[].prices[].resolution: por exemplo,seedance-2.0-fastsempre renderiza em 720p. Uma resolução listada pode não ter preço em alguns modos: confiramodes[].prices; pedir cotação para ela retorna422 price_unavailable.durations:{ min, max, default }em segundos para vídeos,nullpara imagens.modes[].needs: quais imagens o modo exige (image→image_url,lastImage→last_image_url,references→reference_images, atémaxReferences).modes[].aspectRatios: valores permitidos deaspect_ratio.modes[].prices: preço unitário por resolução.
{
"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" }]
}
]
}
]
}Quais modelos posso chamar?
Uma só chave de API chama todos os 17 modelos: 13 de vídeo e 4 de imagem da ByteDance, Kuaishou, MiniMax e Alibaba, entre eles Seedance, Kling, Wan e MiniMax H3. Todos os modelos usam o mesmo formato de requisição; defina model com o id de modelo da API indicado abaixo.
| Modelo | Fornecedor | Id do modelo na API | Tipo |
|---|---|---|---|
| Seedance 2.5 | ByteDance | seedance-2.5 | Vídeo |
| Seedance 2.0 | ByteDance | seedance-2.0 | Vídeo |
| Seedance 2.0 Fast | ByteDance | seedance-2.0-fast | Vídeo |
| MiniMax H3 | MiniMax | minimax-h3 | Vídeo |
| MiniMax H3 Max | MiniMax | minimax-h3-max | Vídeo |
| MiniMax H3 Max Turbo | MiniMax | minimax-h3-max-turbo | Vídeo |
| Wan 3.0 | Alibaba | wan-3.0 | Vídeo |
| Wan 3.0 Prime | Alibaba | wan-3.0-prime | Vídeo |
| Kling 3.0 Pro | Kuaishou | kling-3.0-pro | Vídeo |
| Kling 3.0 Standard | Kuaishou | kling-3.0-standard | Vídeo |
| Kling 3.0 4K | Kuaishou | kling-3.0-4k | Vídeo |
| Kling 4.0 | Kuaishou | kling-4.0 | Vídeo |
| Happy Horse 1.1 | Alibaba | happy-horse-1.1 | Vídeo |
| Seedream 5.0 Pro | ByteDance | seedream-5.0-pro | Imagem |
| Seedream 5.0 | ByteDance | seedream-5.0 | Imagem |
| Qwen3 Image | Alibaba | qwen3-image | Imagem |
| Qwen3 Pro Image | Alibaba | qwen3-pro-image | Imagem |
Modelos por tarefa, com os modos aceitos e exemplos de preço: image to video (em inglês), AI video generator (em inglês) e AI image generator (em inglês).
Erros
Os erros vêm em JSON: { "error": { "code", "message", "reason"?, "details"?, "request_id" } }. Tome decisões com base em code; as mensagens podem mudar. Toda resposta traz o cabeçalho x-request-id (mesmo valor de request_id); informe-o ao falar com o suporte para que a gente encontre a requisição.
| Status | Código | O que fazer |
|---|---|---|
| 400 | invalid_request | O corpo, a query ou um cabeçalho é inválido. details lista cada campo e o problema; corrija a requisição. |
| 400 | template_conflict | Uma requisição com template aceita só template, images e, opcionalmente, prompt, resolution e aspect_ratio; o template define o modelo, o modo, a duração e as demais configurações. Remova os outros campos. |
| 401 | invalid_api_key | A chave está ausente, malformada, desativada, expirada ou excluída. |
| 402 | insufficient_balance | Adicione saldo à sua carteira. |
| 402 | key_spend_limit_reached | Aumente o limite de gastos da chave ou use outra chave. |
| 402 | daily_spend_limit_reached | Sua conta atingiu o limite de gastos diário; ele é zerado às 00:00 UTC. Fale com o suporte para aumentá-lo. |
| 403 | org_frozen | Sua conta está congelada. Fale com o suporte. |
| 404 | quote_not_found | A cotação não existe na sua conta. Peça uma nova cotação. |
| 404 | task_not_found | A geração não existe na sua conta. Confira o id. |
| 404 | output_not_found | A geração não foi concluída com sucesso ou não tem resultado com esse índice. |
| 404 | template_not_found | Não existe template com este id. Liste os templates disponíveis com GET /v1/templates. |
| 404 | not_found | Endpoint desconhecido. Confira o método e o caminho. |
| 409 | quote_already_used | Peça uma nova cotação. |
| 409 | not_cancelable | Só é possível cancelar gerações na fila (queued). |
| 410 | quote_expired | Peça uma nova cotação. |
| 410 | output_expired | O arquivo armazenado expirou e não pode mais ser baixado. |
| 410 | template_retired | Este template não está mais disponível. Escolha outro em GET /v1/templates; as gerações que você já fez não são afetadas. |
| 413 | payload_too_large | O corpo da requisição passa de 64 KB. Encurte o prompt ou envie menos URLs de imagem. |
| 422 | idempotency_conflict | Esta Idempotency-Key já foi usada em outra requisição. Use uma chave nova. |
| 422 | quote_mismatch | A entrada altera o preço (modelo, modo, resolução ou duração). Peça uma nova cotação para esta entrada. |
| 422 | price_unavailable | Esta combinação não tem preço. Escolha uma das listadas por GET /v1/models. |
| 422 | content_rejected | Recusado pela moderação de conteúdo ou pela política de segurança do provedor do modelo; error.reason indica qual (prompt, input_image ou model). Altere o prompt ou as imagens; você não foi cobrado. |
| 429 | rate_limited | Aguarde os segundos indicados em Retry-After e tente de novo. |
| 429 | concurrency_limit_reached | Há gerações demais em andamento na sua conta. Espere uma terminar (veja Retry-After) ou fale com o suporte para aumentar o limite. |
| 500 | internal_error | Erro inesperado no servidor. Tente de novo com backoff; informe o request_id se falar com o suporte. |
Em respostas 5xx inesperadas ou erros de rede, tente de novo com backoff. Ao repetir uma requisição de criação, reutilize a mesma Idempotency-Key para nunca ser cobrado duas vezes.
Limites de taxa
Os limites são por minuto. Ultrapassá-los retorna 429 rate_limited com o cabeçalho Retry-After (em segundos).
| Escopo | Limite |
|---|---|
| Por chave — escritas (cotação, criação, cancelamento) | 60 / min |
| Por chave — leituras (consulta, listagem, eventos, resultados) | 600 / min |
| Por conta — escritas somando todas as chaves | 120 / min |
| Por IP — modelos, preços e requisições com chave recusada | 120 / min |
Uma chave também pode ter no máximo 4 requisições de criação em andamento ao mesmo tempo; as que passarem disso também recebem 429. Os limites podem ser ajustados.
Cada conta também tem dois limites de uso: por padrão, 2 gerações em andamento ao mesmo tempo e US$100 de gasto por dia UTC. Ultrapassá-los retorna 429 concurrency_limit_reached (com Retry-After) ou 402 daily_spend_limit_reached. Fale com o suporte para aumentá-los. Uma geração conta para o limite de gerações em andamento enquanto estiver em queued, generating ou checking, seja qual for o modelo ou a chave que a criou. Ela deixa de contar ao chegar a moderating, succeeded, failed ou canceled. As gerações que você inicia no app web contam para o mesmo limite.
Idempotência
POST /v1/generationsexige o cabeçalhoIdempotency-Key: de 1 a 200 caracteres ASCII imprimíveis, sem espaços. Use um valor novo, como um UUID, para cada nova geração.- As chaves são únicas dentro da sua conta e compartilhadas por todas as chaves de API dela.
- Repetir uma requisição com a mesma chave, a mesma cotação e a mesma entrada devolve a geração original com
200e não cobra de novo. - Usar a mesma chave com outro
quote_id, outroinput(inclusive o prompt e as URLs das imagens) ou outracallback_urlretorna422 idempotency_conflict. - Cotações e cancelamentos não usam Idempotency-Key. Repetir um cancelamento que já deu certo retorna
409 not_cancelable; confira o status comGET.
Perguntas frequentes
Pedir uma cotação custa alguma coisa?
POST /v1/generations/quote só calcula o preço da sua entrada; nada é reservado nem cobrado até você criar uma geração com essa cotação.Preciso de uma chave de API para cada modelo?
model na solicitação.