Avatar
O Gustavo ou a Helena fala no seu site: voz e boca sincronizadas, desenhadas no navegador do seu usuário pelo SDK web. O avatar lê um texto com a própria voz ou dubla um áudio seu. R$ 0,20 por minuto de fala, com a voz incluída.
4 min de leitura · atualizado em 13/10/2026Os avatares
- Gustavo (
avatar: "gustavo", o padrão): o anfitrião. Voz masculina, tom direto e amigável. - Helena (
avatar: "helena"): a atendente, o exemplo de avatar de atendimento para o seu produto. Voz feminina, tom acolhedor.
A opção avatar do SDK escolhe o rosto, a voz e a boca; sem ela, fala o Gustavo. No WebSocket, é o parâmetro ?avatar= da URL. O preço é o mesmo para os dois.
Como funciona
- O seu servidor pede um token temporário com o escopo
avatar(POST /v1/tokens), depois de conferir o login do seu usuário. - A página carrega o SDK de
https://int8labs.com/sdk/avatar/v1.jse cria o avatar num elemento seu. - O SDK abre o WebSocket
/v1/avatar/streamcom o token e o avatar, toca o som e move a boca no tempo certo.
A chave nunca vai para o navegador: só o token, que vence em minutos.
No seu site
import { createAvatar } from "https://int8labs.com/sdk/avatar/v1.js";// O token vem do seu backend (POST /v1/tokens com o escopo avatar). A chave nunca vai para o navegador.// Passe uma função: o SDK a chama ao conectar e de novo quando o token vence, e reconecta sozinho.// Se ela lançar um erro, a fala rejeita com o código "token" e o status do erro (402, 401…).const pedirToken = async () => {const resposta = await fetch("/int8/token", { method: "POST" });if (!resposta.ok) throw Object.assign(new Error("Não deu para pegar o token."), { status: resposta.status });return (await resposta.json()).token;};const avatar = await createAvatar({container: document.querySelector("#avatar"),avatar: "gustavo", // ou "helena": o rosto, a voz e a bocatoken: pedirToken,apiUrl: "https://api.int8labs.com",});// O done chega com o custo enquanto o som ainda toca; o speak() resolve quando o som termina.avatar.on("done", ({ audioMs, costBrl }) => console.log(`${audioMs} ms, R$ ${costBrl}`));avatar.on("error", ({ code, status, message }) => console.error(code, status, message));// O navegador só deixa o som tocar depois de um gesto: fale dentro de um clique.// Sem o clique, a fala rejeita com o código "autoplay".document.querySelector("#falar").addEventListener("click", async () => {await avatar.speak("Olá! Como posso ajudar?");await avatar.speak("Posso confirmar a sua consulta?"); // fala depois da primeira, sem cortar});
O token vem de uma rota sua, como esta:
curl https://api.int8labs.com/v1/tokens \-H "Authorization: Bearer $INT8_API_KEY" \-H "Content-Type: application/json" \-d '{"ttl_seconds": 600, "scopes": ["avatar"]}'
Passe o token como uma função, como no exemplo: o SDK a chama ao conectar e de novo quando o token vence (fechamento 4401), e reconecta sozinho. Com um token fixo (texto), o 4401 vira o erro api_key_expired e a conversa acaba.
Quando a função de token falha, a fala rejeita (e o evento error sai) com o código token, a mensagem e o status numérico do erro que ela lançou: um 402, por exemplo, quando o seu backend recusa o token por falta de saldo. Por isso o exemplo lança o erro com o status da resposta.
Um avatar fora da lista rejeita o createAvatar com o código invalid_request, antes de baixar qualquer coisa.
Modo texto
avatar.speak(texto) fala até 1.000 caracteres por vez, com a voz do avatar escolhido. A promessa resolve quando o som termina de tocar, junto com o evento end ({ interrupted: false }), e devolve a duração (audioMs), o custo (costBrl) e o saldo (balanceBrl). O evento done chega antes, com esses números e o som ainda tocando. Assim, dois await avatar.speak() seguidos falam um depois do outro, sem corte.
Se stop() ou uma nova fala interromper o speak(), a promessa rejeita com o código interrupted e o end sai com { interrupted: true }. Trate-o como um fim normal, não como falha. A parte que já foi falada é cobrada. O SDK também recusa uma chave int8_live_: use sempre o token temporário int8_tmp_, gerado no seu servidor.
O navegador só deixa o som tocar depois de um gesto do usuário, como um clique. Por isso o exemplo chama speak() dentro de um clique. Sem o gesto, o som fica bloqueado: o done sai, porque a fala foi gerada e é cobrada, e logo depois a promessa rejeita com o código autoplay e a mensagem "O navegador bloqueou o som: chame speak() depois de um clique.". O evento error sai junto. O speakAudio() segue a mesma regra.
Modo áudio próprio
Com um áudio seu (gravado, ou de outro motor de voz), o avatar só mexe a boca:
// A boca dubla um áudio seu: o SDK decodifica no navegador, reamostra e toca sincronizado com o rosto.// Como o speak(), chame dentro de um clique: sem o gesto, o navegador bloqueia o som.document.querySelector("#falar").addEventListener("click", async () => {const resposta = await fetch("/audios/boas-vindas.mp3");await avatar.speakAudio(await resposta.arrayBuffer());});// Quando a página sair: fecha a conexão e libera o WebGL.avatar.destroy();
O SDK decodifica o arquivo no navegador, reamostra e manda no modo áudio próprio; o som toca do seu lado, sincronizado com o rosto. Até 5 minutos por fala.
Protocolo
Para usar sem o SDK (num app nativo, por exemplo), o WebSocket manda o áudio (0x01) e os pacotes de rosto (0x02, 25 quadros por segundo). A sincronia da boca com o som fica do seu lado, pela posição do player.
import asyncioimport jsonimport osimport websockets # pip install "websockets>=14"async def main() -> None:# avatar=gustavo (o padrão) ou helena: o rosto, a voz e a boca.async with websockets.connect("wss://api.int8labs.com/v1/avatar/stream?avatar=helena",additional_headers={"Authorization": f"Bearer {os.environ['INT8_API_KEY']}"},) as ws:await ws.send(json.dumps({"type": "speak", "text": "Olá! Como posso ajudar?"}))audio, pacotes = bytearray(), 0async for mensagem in ws:if isinstance(mensagem, bytes):if mensagem[0] == 0x01:audio += mensagem[3:] # PCM16 LE mono, 44100 Hzelif mensagem[0] == 0x02:pacotes += 1 # pacote de rosto, para o renderizadorcontinueevento = json.loads(mensagem)if evento["type"] == "done":print(evento["audio_ms"], "ms;", pacotes, "pacotes de rosto; R$", evento["cost_brl"])breakasyncio.run(main())
As mensagens estão na referência do WebSocket do avatar.
Limites e cobrança
- Até 5 conexões de avatar abertas por organização; a sexta fecha com 4429.
- Texto de até 1.000 caracteres; áudio próprio de até 5 minutos por fala.
- A cobrança é por segundo de fala, voz incluída. O saldo é conferido antes de cada fala.