swapAPI

API SWAP · v1

Personagens em cena, por uma chamada HTTP.

Troque quem aparece num vídeo, anime uma imagem ou gere uma cena a partir de texto. A API usa o mesmo saldo de créditos da sua conta Swap. Cada chave tem permissões, limite de requisições e um teto de créditos próprios.

Endereço basehttps://api.tryaswap.com/v1
Formatoapplication/json, exceto envio e download de mídia
Sucesso{"data": …}
Erro{"error": {"code", "message"}, "requestId"}
Antes de integrar: consulte GET /v1/capabilities. Enquanto generation.available for false, a cotação e a criação de gerações respondem 503 engine_unavailable e nenhum crédito é reservado. Envio de arquivos, saldo, extrato e chaves já funcionam.

Três modos de geração

character_replacement

Troca de personagem

Seu vídeo em sourceAssetId e o personagem em referenceAssetId (imagem) ou personaId. Movimento, câmera e luz continuam os do vídeo.

image_to_video

Imagem para vídeo

Uma imagem em sourceAssetId e um prompt com a ação.

text_to_video

Texto para vídeo

Só o prompt, sem arquivo.

Autenticação

Crie uma chave em Minhas chaves. O segredo começa com swap_sk_ e aparece uma única vez; a Swap guarda apenas um hash. Envie-o em toda requisição:

Authorization: Bearer swap_sk_key_…

A chave só funciona no servidor. Requisições de navegador com cabeçalho Origin de outros sites são recusadas com 403 origin_forbidden. Chaves não compram créditos nem gerenciam outras chaves; isso é feito no painel.

PermissãoLibera
jobs:writeCotar e criar gerações. Reserva e consome créditos.
jobs:readListar e acompanhar gerações e baixar o vídeo final.
assets:writeEnviar e excluir vídeos e imagens.
assets:readListar arquivos e baixar o conteúdo.
balance:readConsultar o saldo.
usage:readLer o extrato de créditos.
personas:readLer os personagens criados no estúdio.

Ao criar a chave você define: permissões, teto de créditos (soma vitalícia de gasto e reservas da chave), requisições por minuto (1–120, padrão 60) e validade (até 1 ano). Uma conta tem até 20 chaves ativas.

Primeira geração: troca de personagem

Seis passos: enviar o vídeo, enviar a imagem do personagem, cotar, criar, acompanhar e baixar. Defina a variável SWAP_API_KEY com uma chave que tenha assets:write, jobs:write e jobs:read.

1Envie o vídeo da cena

O corpo é o arquivo binário. Guarde o asset.id da resposta. MP4 ou WebM, até 20 MiB.

curl https://api.tryaswap.com/v1/assets \
  -H "Authorization: Bearer $SWAP_API_KEY" \
  -H "Content-Type: video/mp4" \
  -H "X-Swap-Filename: cena.mp4" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data-binary @cena.mp4
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { randomUUID } from 'node:crypto';

const API = 'https://api.tryaswap.com/v1';
const auth = { Authorization: `Bearer ${process.env.SWAP_API_KEY}` };

async function call(path, init = {}) {
  const response = await fetch(API + path, { ...init, headers: { ...auth, ...init.headers } });
  const body = await response.json();
  if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.requestId})`);
  return body.data;
}

async function upload(file, type) {
  const { asset } = await call('/assets', {
    method: 'POST',
    body: await readFile(file),
    headers: { 'Content-Type': type, 'X-Swap-Filename': encodeURIComponent(basename(file)), 'Idempotency-Key': randomUUID() },
  });
  return asset.id;
}

const sourceAssetId = await upload('cena.mp4', 'video/mp4');
import os, time, uuid
from urllib.parse import quote as encode
import requests

API = "https://api.tryaswap.com/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SWAP_API_KEY']}"

def call(method, path, **kwargs):
    response = session.request(method, API + path, timeout=120, **kwargs)
    body = response.json()
    if not response.ok:
        raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} ({body.get('requestId')})")
    return body["data"]

def upload(path, mime):
    with open(path, "rb") as file:
        data = call("POST", "/assets", data=file, headers={
            "Content-Type": mime,
            "X-Swap-Filename": encode(os.path.basename(path)),
            "Idempotency-Key": str(uuid.uuid4()),
        })
    return data["asset"]["id"]

source_asset_id = upload("cena.mp4", "video/mp4")

2Envie a imagem do personagem

JPEG, PNG ou WebP, de preferência de corpo inteiro e fundo limpo. Para usar um personagem do estúdio, pule este passo e envie personaId (veja Listar personagens).

curl https://api.tryaswap.com/v1/assets \
  -H "Authorization: Bearer $SWAP_API_KEY" \
  -H "Content-Type: image/png" \
  -H "X-Swap-Filename: personagem.png" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data-binary @personagem.png
const referenceAssetId = await upload('personagem.png', 'image/png');
reference_asset_id = upload("personagem.png", "image/png")

3Cote a geração

A cotação valida os arquivos e informa quantos créditos serão reservados, sem reservar nada. sufficient considera o saldo da conta e o teto restante da chave.

curl https://api.tryaswap.com/v1/jobs/quote \
  -H "Authorization: Bearer $SWAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mode":"character_replacement","sourceAssetId":"asset_…","referenceAssetId":"asset_…","durationSeconds":5,"aspectRatio":"9:16"}'
const input = { mode: 'character_replacement', sourceAssetId, referenceAssetId, durationSeconds: 5, aspectRatio: '9:16' };
const json = { 'Content-Type': 'application/json' };

const quote = await call('/jobs/quote', { method: 'POST', headers: json, body: JSON.stringify(input) });
if (!quote.sufficient) throw new Error(`Saldo insuficiente: a geração reserva ${quote.reservedCredits} créditos.`);
job_input = {"mode": "character_replacement", "sourceAssetId": source_asset_id,
             "referenceAssetId": reference_asset_id, "durationSeconds": 5, "aspectRatio": "9:16"}

quote = call("POST", "/jobs/quote", json=job_input)
if not quote["sufficient"]:
    raise RuntimeError(f"Saldo insuficiente: a geração reserva {quote['reservedCredits']} créditos.")

4Crie a geração

Gere um Idempotency-Key por geração e reutilize-o se precisar repetir a chamada: a Swap devolve o mesmo job, sem cobrar de novo. A resposta 202 traz o job na fila.

curl https://api.tryaswap.com/v1/jobs \
  -H "Authorization: Bearer $SWAP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f6d2c1e-8b4a-4f0e-9a7d-5c2b1e0f9a8d" \
  -d '{"mode":"character_replacement","sourceAssetId":"asset_…","referenceAssetId":"asset_…","durationSeconds":5,"aspectRatio":"9:16"}'
// Guarde esta chave com o pedido do seu usuário para repetir com segurança.
const idempotencyKey = randomUUID();
let job = await call('/jobs', { method: 'POST', headers: { ...json, 'Idempotency-Key': idempotencyKey }, body: JSON.stringify(input) });
# Guarde esta chave com o pedido do seu usuário para repetir com segurança.
idempotency_key = str(uuid.uuid4())
job = call("POST", "/jobs", json=job_input, headers={"Idempotency-Key": idempotency_key})

5Acompanhe até terminar

Consulte a cada 5–10 segundos. queued e running continuam; succeeded traz outputUrl; failed traz errorCode e devolve a reserva inteira.

curl https://api.tryaswap.com/v1/jobs/job_… \
  -H "Authorization: Bearer $SWAP_API_KEY"
while (job.state === 'queued' || job.state === 'running') {
  await new Promise(resolve => setTimeout(resolve, 8000));
  job = await call(`/jobs/${job.id}`);
}
if (job.state === 'failed') throw new Error(`Geração falhou (${job.errorCode}). A reserva foi devolvida.`);
while job["state"] in ("queued", "running"):
    time.sleep(8)
    job = call("GET", f"/jobs/{job['id']}")
if job["state"] == "failed":
    raise RuntimeError(f"Geração falhou ({job['errorCode']}). A reserva foi devolvida.")

6Baixe o vídeo

curl https://api.tryaswap.com/v1/jobs/job_…/output \
  -H "Authorization: Bearer $SWAP_API_KEY" \
  -o resultado.mp4
import { writeFile } from 'node:fs/promises';

const video = await fetch(API + job.outputUrl.replace('/v1', ''), { headers: auth });
await writeFile('resultado.mp4', Buffer.from(await video.arrayBuffer()));
console.log(`Pronto: ${job.chargedCredits} créditos cobrados.`);
with session.get(API + job["outputUrl"].removeprefix("/v1"), stream=True, timeout=300) as video:
    video.raise_for_status()
    with open("resultado.mp4", "wb") as file:
        for chunk in video.iter_content(1 << 20):
            file.write(chunk)
print(f"Pronto: {job['chargedCredits']} créditos cobrados.")

Créditos e cobrança

Estúdio, app e API usam um único saldo. Compre pacotes ou assine um plano no painel; o saldo só muda depois que o pagamento é confirmado. Créditos comprados não expiram.

Geração (720p)Créditos
Troca de personagem, cada 5 s iniciados10 (5 s = 10, 10 s = 20)
Imagem para vídeo, cada 5 s iniciados8
Texto para vídeo, cada 5 s iniciados8

A tabela também vem em GET /v1/capabilities (generation.pricing). A cotação (POST /v1/jobs/quote) é sempre a referência para cada pedido.

  1. Reserva. Ao criar a geração, a Swap reserva o valor da cotação. Se faltar saldo, a resposta é 402 insufficient_credits; se faltar teto na chave, 402 key_credit_limit.
  2. Cobrança. Ao terminar, cobra o custo real (chargedCredits), nunca acima do reservado, e devolve a diferença.
  3. Devolução. Se a geração falhar, toda a reserva volta para o saldo.

Acompanhe tudo em GET /v1/balance e GET /v1/usage. Um estorno de pagamento pode retirar créditos (revoked) ou gerar um ajuste (debt); enquanto houver pendência, frozen é true e novas gerações respondem 409 billing_financial_review. Gerações já iniciadas terminam normalmente.

Limites

LimiteValor
Requisições por chaveDefinido na chave, 1–120 por minuto (padrão 60). Cabeçalhos X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset em toda resposta autenticada.
Excedeu o limite429 rate_limited (chave) ou 429 ip_rate_limited (endereço). Espere o tempo de Retry-After.
Arquivo20 MiB; JPEG, PNG, WebP, MP4 ou WebM.
Arquivos por conta256 MiB e 100 arquivos no total; 20 envios por hora.
Duração da geração1 a 30 segundos (durationSeconds).
Paginação?limit= até 100; siga nextCursor em ?cursor=.
Chaves ativas20 por conta.

Erros

Todo erro traz um code estável para o seu código tratar, uma message em português para pessoas e um requestId. Informe o requestId ao suporte.

{
  "error": { "code": "insufficient_credits", "message": "Saldo de créditos insuficiente." },
  "requestId": "req_4b0e2a…"
}
HTTPcodeO que fazer
400invalid_field, unknown_field, invalid_json, invalid_idempotency_keyCorrija a requisição; não repita igual.
401api_key_required, invalid_api_keyEnvie uma chave válida. Chaves expiradas ou revogadas não voltam a funcionar.
402insufficient_credits, key_credit_limitCompre créditos ou aumente o teto criando outra chave.
403insufficient_scope, origin_forbiddenUse uma chave com a permissão certa, a partir do servidor.
404not_found, asset_not_foundO recurso não existe ou é de outra conta.
409idempotency_conflict, output_not_ready, asset_in_use, billing_financial_reviewVeja a mensagem: use outra chave de idempotência, espere o fim da geração ou resolva a pendência.
413 / 415asset_too_large, unsupported_media_type, media_signature_mismatchEnvie um arquivo aceito, dentro do tamanho.
422asset_kind_mismatch, mode_unavailableConfira o tipo do arquivo para o modo escolhido.
429rate_limited, ip_rate_limited, upload_rate_limitEspere Retry-After e tente de novo.
503engine_unavailable, storage_unavailableServiço temporariamente indisponível. Nenhum crédito foi reservado; tente mais tarde.
500internal_errorRepita com o mesmo Idempotency-Key. Se persistir, fale com o suporte.

Idempotência

POST /v1/jobs e POST /v1/assets exigem Idempotency-Key (16–128 caracteres A–Z a–z 0–9 _ . : -). Use um UUID por operação lógica e guarde-o junto com o pedido do seu usuário.

  • Mesma chave e mesmo conteúdo: a Swap devolve o recurso original com replayed: true, sem nova cobrança nem novo arquivo.
  • Mesma chave com conteúdo diferente: 409 idempotency_conflict.
  • Timeout, queda de rede ou 5xx: repita com a mesma chave. Nunca gere uma chave nova para tentar de novo uma geração que pode ter sido aceita.

Segurança

  • Guarde a chave em variável de ambiente ou cofre de segredos. Nunca a coloque em aplicativo, site, repositório ou log.
  • Crie uma chave por integração e por ambiente, com só as permissões necessárias.
  • Use o teto de créditos: se a chave vazar, o prejuízo para nele.
  • Para trocar uma chave: crie a nova, publique, confirme o uso e revogue a antiga no painel.
  • Se suspeitar de vazamento, revogue imediatamente. A revogação vale na próxima requisição.

Agentes de IA

Para usar a API com um agente ou gerar um cliente, aponte para a especificação OpenAPI 3.1 ou para o resumo em texto llms.txt. Dê ao agente uma chave própria, com teto de créditos baixo.

Mudanças

08/10/2026
Tabela de créditos por geração em generation.pricing: troca 720p a 10 créditos por bloco de 5 s; imagem e texto para vídeo a 8.
07/10/2026 · v1.0
Domínio api.tryaswap.com. Chaves com permissões de arquivos e personagens, POST /v1/jobs/quote, GET /v1/jobs/{id}/output, referenceAssetId e cabeçalhos X-RateLimit-*.

REFERÊNCIA

Endpoints

Gerada da especificação OpenAPI. Cada rota mostra a permissão exigida.

Carregando a referência…