Makernice

Documentação da API Makernice: início rápido

Equipe Makernice ·

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

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":"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

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":"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

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
# em succeeded: faça GET em outputs[0].url com sua chave para receber um 302 para o arquivo

5. Baixe o arquivo

curl
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:

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: 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 (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);
});

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
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.

GEThttps://api.makernice.com/v1/pricing
Exemplo de resposta (valores ilustrativos)
{
  "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 aconteceubillingCobrada?
A geração é concluída com sucessochargedSim, o valor cotado
A geração falha (error.code generation_failed, upstream_unavailable ou invalid_request)releasedNão
Ela passa do tempo máximo de execução (error.code: timeout)releasedNão
Você a cancela enquanto está em queuedreleasedNã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)releasedNão
O resultado concluído é bloqueado pela moderação do resultado (output state: blocked)chargedSim; fale com o suporte para uma revisão manual
O resultado está sendo confirmado (status: checking)heldSó se for confirmado que foi concluído com sucesso

Exemplos de cobrança

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.

POSThttps://api.makernice.com/v1/generations/quote
CampoTipoDescrição
modelstringObrigatório. Um id de modelo de GET /v1/models.
modestringObrigatório. t2v, i2v, flf, r2v, t2i ou i2i; cada modelo lista os modos que aceita.
promptstringObrigatório. De 1 a 4.000 caracteres por padrão; alguns modelos aceitam menos (veja maxPromptChars em GET /v1/models).
durationintegerSó para vídeo. Segundos, dentro da faixa do modelo; o padrão é a duração padrão do modelo.
resolutionstringUma das resolutions do modelo; o padrão é defaultResolution. Não é aceito quando o modelo não lista nenhuma.
aspect_ratiostringUm dos aspectRatios do modo.
image_urlstringPrimeiro frame. Obrigatório para i2v e flf.
last_image_urlstringÚltimo frame. Obrigatório para flf.
reference_imagesstring[]Obrigatório para r2v e i2i; no máximo o maxReferences do modo.
seedintegerOpcional. De 0 a 2.147.483.647. A mesma seed com as mesmas configurações dá resultados mais parecidos.
generate_audiobooleanOpcional. Define se o vídeo ganha trilha sonora.
prompt_expansionstringOpcional. off, balanced ou quality: quanto o modelo pode reescrever o seu prompt.
negative_promptstringOpcional. Até 2.000 caracteres descrevendo o que evitar.
watermarkbooleanOpcional. 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
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"}'
Exemplo de resposta (valores ilustrativos)
{
  "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 casoO que significa
expires_at12 minutos depois que a cotação foi criada. Crie a geração antes disso.
amountPreç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_versionA 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 minutosCriar 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ção422 quote_mismatch. Mudar só o prompt mantém a cotação.
Já usou a cotação409 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).

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":"Travelling lento por dentro de uma estufa","duration":5,"resolution":"720p"}}'
  • Responde 202 com a geração (status: queued, billing: held). Uma nova tentativa com a mesma Idempotency-Key responde 200 com 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 retorna 402 key_spend_limit_reached.
  • callback_url opcional 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.

GEThttps://api.makernice.com/v1/generations/{id}
StatusSignificado
queuedAguardando para começar. Ainda pode ser cancelada.
generatingO modelo está trabalhando nela.
moderatingA 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.
succeededPronto. O valor foi cobrado e os outputs estão disponíveis.
failedNão terminou. A reserva é liberada; error traz um código e uma mensagem.
canceledCancelada enquanto estava na fila. A reserva é liberada.
checkingEstamos 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.

Exemplo de resposta (valores ilustrativos)
{
  "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.

GEThttps://api.makernice.com/v1/generations/{id}/events
Exemplo de resposta (valores ilustrativos)
{
  "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.

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"
  • Adicione ?download=1 para receber o arquivo como anexo. Isso funciona quando o arquivo está stored; enquanto estiver pending ou degraded, o redirecionamento vai para o arquivo original e o parâmetro não tem efeito.
  • O curl -L não envia o seu cabeçalho Authorization ao 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 retorna 404 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 retorna 410 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.

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", … }

Listar gerações

Lista as gerações da sua conta, das mais recentes para as mais antigas — inclusive as criadas no console.

GEThttps://api.makernice.com/v1/generations
QueryDescrição
limitTamanho da página, de 1 a 100. Padrão 20.
cursorO next_cursor da página anterior.
statusUm ou mais status, separados por vírgula, por exemplo queued,generating.
typevideo ou image.
modelUm id de modelo.
qParte de um id de geração ou um texto contido no prompt (ambos sem diferenciar maiúsculas e minúsculas).
curl
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.

EventoEnviado quando
generation.succeededA geração foi concluída com sucesso e cobrada.
generation.failedA geração falhou; o valor reservado foi liberado.
generation.canceledA geração foi cancelada; o valor reservado foi liberado.

Requisição

Exemplo de requisição (valores ilustrativos)
POST /your/webhook/path HTTP/1.1
Content-Type: application/json
Makernice-Signature: t=1791364443,v1=5f0c2d…e81a
Makernice-Event-Id: evt_1b7f…
Exemplo de corpo (valores ilustrativos)
{
  "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 de GET /v1/generations/{id}, exceto que outputs[].url é uma URL absoluta.
  • Makernice-Event-Id é igual ao id do 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.

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", ""))

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.

GEThttps://api.makernice.com/v1/models
  • resolutions / defaultResolution: valores permitidos para resolution. Uma lista vazia significa que o modelo tem resolução fixa e você deve omitir resolution (enviá-lo, mesmo com o valor fixo, retorna 400 invalid_request). A faixa fixa é a que aparece em modes[].prices[].resolution: por exemplo, seedance-2.0-fast sempre renderiza em 720p. Uma resolução listada pode não ter preço em alguns modos: confira modes[].prices; pedir cotação para ela retorna 422 price_unavailable.
  • durations: { min, max, default } em segundos para vídeos, null para imagens.
  • modes[].needs: quais imagens o modo exige (image → image_url, lastImage → last_image_url, references → reference_images, até maxReferences).
  • modes[].aspectRatios: valores permitidos de aspect_ratio. modes[].prices: preço unitário por resolução.
Exemplo de resposta (valores ilustrativos)
{
  "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.

ModeloFornecedorId do modelo na APITipo
Seedance 2.5ByteDanceseedance-2.5Vídeo
Seedance 2.0ByteDanceseedance-2.0Vídeo
Seedance 2.0 FastByteDanceseedance-2.0-fastVídeo
MiniMax H3MiniMaxminimax-h3Vídeo
MiniMax H3 MaxMiniMaxminimax-h3-maxVídeo
MiniMax H3 Max TurboMiniMaxminimax-h3-max-turboVídeo
Wan 3.0Alibabawan-3.0Vídeo
Wan 3.0 PrimeAlibabawan-3.0-primeVídeo
Kling 3.0 ProKuaishoukling-3.0-proVídeo
Kling 3.0 StandardKuaishoukling-3.0-standardVídeo
Kling 3.0 4KKuaishoukling-3.0-4kVídeo
Kling 4.0Kuaishoukling-4.0Vídeo
Happy Horse 1.1Alibabahappy-horse-1.1Vídeo
Seedream 5.0 ProByteDanceseedream-5.0-proImagem
Seedream 5.0ByteDanceseedream-5.0Imagem
Qwen3 ImageAlibabaqwen3-imageImagem
Qwen3 Pro ImageAlibabaqwen3-pro-imageImagem

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.

StatusCódigoO que fazer
400invalid_requestO corpo, a query ou um cabeçalho é inválido. details lista cada campo e o problema; corrija a requisição.
400template_conflictUma 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.
401invalid_api_keyA chave está ausente, malformada, desativada, expirada ou excluída.
402insufficient_balanceAdicione saldo à sua carteira.
402key_spend_limit_reachedAumente o limite de gastos da chave ou use outra chave.
402daily_spend_limit_reachedSua conta atingiu o limite de gastos diário; ele é zerado às 00:00 UTC. Fale com o suporte para aumentá-lo.
403org_frozenSua conta está congelada. Fale com o suporte.
404quote_not_foundA cotação não existe na sua conta. Peça uma nova cotação.
404task_not_foundA geração não existe na sua conta. Confira o id.
404output_not_foundA geração não foi concluída com sucesso ou não tem resultado com esse índice.
404template_not_foundNão existe template com este id. Liste os templates disponíveis com GET /v1/templates.
404not_foundEndpoint desconhecido. Confira o método e o caminho.
409quote_already_usedPeça uma nova cotação.
409not_cancelableSó é possível cancelar gerações na fila (queued).
410quote_expiredPeça uma nova cotação.
410output_expiredO arquivo armazenado expirou e não pode mais ser baixado.
410template_retiredEste 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.
413payload_too_largeO corpo da requisição passa de 64 KB. Encurte o prompt ou envie menos URLs de imagem.
422idempotency_conflictEsta Idempotency-Key já foi usada em outra requisição. Use uma chave nova.
422quote_mismatchA entrada altera o preço (modelo, modo, resolução ou duração). Peça uma nova cotação para esta entrada.
422price_unavailableEsta combinação não tem preço. Escolha uma das listadas por GET /v1/models.
422content_rejectedRecusado 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.
429rate_limitedAguarde os segundos indicados em Retry-After e tente de novo.
429concurrency_limit_reachedHá gerações demais em andamento na sua conta. Espere uma terminar (veja Retry-After) ou fale com o suporte para aumentar o limite.
500internal_errorErro 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).

EscopoLimite
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 chaves120 / min
Por IP — modelos, preços e requisições com chave recusada120 / 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/generations exige o cabeçalho Idempotency-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 200 e não cobra de novo.
  • Usar a mesma chave com outro quote_id, outro input (inclusive o prompt e as URLs das imagens) ou outra callback_url retorna 422 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 com GET.

Perguntas frequentes

Pedir uma cotação custa alguma coisa?
Não. 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?
Não. Uma única chave de API chama todos os 17 modelos; para trocar de modelo, mude o campo model na solicitação.