character_replacementTroca 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.
API SWAP · v1
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.
https://api.tryaswap.com/v1application/json, exceto envio e download de mídia{"data": …}{"error": {"code", "message"}, "requestId"}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.character_replacementSeu vídeo em sourceAssetId e o personagem em referenceAssetId (imagem) ou personaId. Movimento, câmera e luz continuam os do vídeo.
image_to_videoUma imagem em sourceAssetId e um prompt com a ação.
text_to_videoSó o prompt, sem arquivo.
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ão | Libera |
|---|---|
jobs:write | Cotar e criar gerações. Reserva e consome créditos. |
jobs:read | Listar e acompanhar gerações e baixar o vídeo final. |
assets:write | Enviar e excluir vídeos e imagens. |
assets:read | Listar arquivos e baixar o conteúdo. |
balance:read | Consultar o saldo. |
usage:read | Ler o extrato de créditos. |
personas:read | Ler 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.
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.
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")
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")
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.")
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})
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.")
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.")
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.
402 insufficient_credits; se faltar teto na chave, 402 key_credit_limit.chargedCredits), nunca acima do reservado, e devolve a diferença.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.
| Limite | Valor |
|---|---|
| Requisições por chave | Definido 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 limite | 429 rate_limited (chave) ou 429 ip_rate_limited (endereço). Espere o tempo de Retry-After. |
| Arquivo | 20 MiB; JPEG, PNG, WebP, MP4 ou WebM. |
| Arquivos por conta | 256 MiB e 100 arquivos no total; 20 envios por hora. |
| Duração da geração | 1 a 30 segundos (durationSeconds). |
| Paginação | ?limit= até 100; siga nextCursor em ?cursor=. |
| Chaves ativas | 20 por conta. |
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…"
}
| HTTP | code | O que fazer |
|---|---|---|
| 400 | invalid_field, unknown_field, invalid_json, invalid_idempotency_key | Corrija a requisição; não repita igual. |
| 401 | api_key_required, invalid_api_key | Envie uma chave válida. Chaves expiradas ou revogadas não voltam a funcionar. |
| 402 | insufficient_credits, key_credit_limit | Compre créditos ou aumente o teto criando outra chave. |
| 403 | insufficient_scope, origin_forbidden | Use uma chave com a permissão certa, a partir do servidor. |
| 404 | not_found, asset_not_found | O recurso não existe ou é de outra conta. |
| 409 | idempotency_conflict, output_not_ready, asset_in_use, billing_financial_review | Veja a mensagem: use outra chave de idempotência, espere o fim da geração ou resolva a pendência. |
| 413 / 415 | asset_too_large, unsupported_media_type, media_signature_mismatch | Envie um arquivo aceito, dentro do tamanho. |
| 422 | asset_kind_mismatch, mode_unavailable | Confira o tipo do arquivo para o modo escolhido. |
| 429 | rate_limited, ip_rate_limited, upload_rate_limit | Espere Retry-After e tente de novo. |
| 503 | engine_unavailable, storage_unavailable | Serviço temporariamente indisponível. Nenhum crédito foi reservado; tente mais tarde. |
| 500 | internal_error | Repita com o mesmo Idempotency-Key. Se persistir, fale com o suporte. |
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.
replayed: true, sem nova cobrança nem novo arquivo.409 idempotency_conflict.5xx: repita com a mesma chave. Nunca gere uma chave nova para tentar de novo uma geração que pode ter sido aceita.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.
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
Gerada da especificação OpenAPI. Cada rota mostra a permissão exigida.
Carregando a referência…