Guia
A SwissAI dá-lhe agentes de voz que atendem chamadas telefónicas. Cria-os no painel com um prompt, um idioma e ferramentas opcionais; cada agente recebe uma URI SIP. Encaminhe as chamadas para essa URI e o agente atende, fala com quem liga e chama os seus endpoints HTTP quando precisa de dados ou quer agir.
Tudo se configura a partir do painel: agentes, ferramentas, os seus clientes e a faturação.
Agentes
Cria e edita os agentes no painel: nome, idioma, scope (o prompt), uma base de conhecimento opcional, os modelos e as ferramentas. Cada agente recebe um id e uma URI SIP, e pode ser atribuído a um dos seus clientes.
Exemplo de um scope
Escreva o scope como se explicasse a um colega novo: quem é o agente, o que pode fazer, o que nunca deve fazer e como termina a chamada.
És a Anna, a assistente da Clínica Dentária Keller em Zurique. Atendes o telefone quando a receção está ocupada ou fechada. O que fazes: - marcar, mudar ou cancelar consultas, usando as ferramentas; - responder a perguntas sobre horário, morada e preços (ver a base de conhecimento). Regras: - fala de forma breve e educada, uma pergunta de cada vez; - antes de marcar, repete dia, hora e nome e espera por um sim claro; - nunca dês conselhos médicos: em caso de dor ou urgência propõe a primeira consulta livre e dá o número de emergência; - se não puderes ajudar, anota nome e telefone e diz que a clínica vai ligar de volta. Termina a chamada repetindo o que foi combinado.
Ferramentas
Definição de ferramenta
Uma ferramenta é um endpoint HTTP do seu lado descrito para o modelo com um esquema JSON. O modelo decide quando a chamar e com que argumentos; a plataforma faz o pedido HTTP.
{
"name": "book_appointment",
"description": "Marca uma consulta. Chama-a só depois de quem liga confirmar dia, hora e nome.",
"method": "POST",
"url": "https://api.example.ch/clinics/{{clinic}}/appointments",
"headers": { "Authorization": "Bearer sk_…" },
"content_type": "application/json",
"body": "{ \"start\": \"{{start}}\", \"name\": \"{{name}}\", \"phone\": \"{{phone}}\" }",
"schema": {
"type": "object",
"properties": {
"clinic": { "type": "string", "description": "Código da clínica: sempre 'zurich'" },
"start": { "type": "string", "description": "Início, yyyy-MM-dd HH:mm, ex. 2026-10-06 10:30" },
"name": { "type": "string", "description": "Nome e apelido de quem liga" },
"phone": { "type": "string", "description": "Número de telefone de quem liga, só dígitos" }
},
"required": ["clinic", "start", "name", "phone"]
}
}
| Campo | Para | Descrição |
|---|---|---|
| name | modelo | Nome da função, letras e underscores. |
| description | modelo | O que faz e quando a chamar. Escreva-o para o modelo. |
| schema | modelo | Esquema JSON dos argumentos. |
| response_schema | modelo | Opcional. Forma da sua resposta, para o modelo a poder ler. |
| url | runtime | O seu endpoint, HTTPS. |
| method | runtime | GET (argumentos na query string) ou POST (argumentos como corpo JSON). |
| content_type | runtime | Normalmente application/json. |
| headers | runtime | Opcional. Cabeçalhos enviados em cada chamada, ex. Authorization; os valores podem conter {{parâmetros}}. |
| body | runtime | Opcional. Modelo do corpo com {{parâmetros}}. Vazio: os parâmetros não usados no url ou nos cabeçalhos são enviados na query string (GET, DELETE) ou como corpo JSON. |
Parâmetros e placeholders
Escreva {{nome}} no url, nos valores dos cabeçalhos ou no corpo. Cada placeholder tem de ser declarado em schema.properties com uma descrição: o modelo preenche-o a partir da conversa e o runtime substitui-o (codificado no url, escapado num corpo JSON). O painel constrói o esquema a partir dos placeholders e verifica a ferramenta com o Claude antes de guardar.
Como o agente o chama
Cada pedido leva o cabeçalho token com o tool_token do agente. Responda com 200 e um corpo JSON:
- Sucesso: { "success": true, "message": "…" } mais quaisquer dados. A mensagem destina-se a ser transmitida a quem liga.
- Erro de negócio: { "success": false, "error": "…" }. Escreva o erro como uma frase para o modelo: o que perguntar ou propor em alternativa.
- 401 / 500: o agente pede desculpa e propõe ligar de volta. Mantenha as respostas abaixo de poucos segundos: quem liga está à espera na linha.
O seu endpoint: exemplos
Este é o pedido que a plataforma envia para a ferramenta acima, e um endpoint mínimo que lhe responde. Verifique primeiro o cabeçalho token: é o tool token que definiu no agente.
POST /clinics/zurich/appointments HTTP/1.1 Host: api.example.ch Authorization: Bearer sk_… token: 5f1c… # o tool token do agente Content-Type: application/json { "start": "2026-10-06 10:30", "name": "Anna Keller", "phone": "41791234567" }
app.post('/clinics/:clinic/appointments', express.json(), async (req, res) => { // 1. só o seu agente pode chamar if (req.get('token') !== process.env.SWISSAI_TOOL_TOKEN) return res.status(401).end(); const { start, name, phone } = req.body; // 2. erro de negócio: uma frase sobre a qual o agente pode agir if (!(await isFree(req.params.clinic, start))) return res.json({ success: false, error: "Essa hora está ocupada. Proponha 11:00 ou 14:30 no mesmo dia." }); // 3. sucesso: a mensagem é o que o agente diz a quem liga await book(req.params.clinic, start, name, phone); res.json({ success: true, message: "Consulta marcada para " + start + '.' }); });
<?php // appointments.php header('Content-Type: application/json'); if (($_SERVER['HTTP_TOKEN'] ?? '') !== getenv('SWISSAI_TOOL_TOKEN')) { http_response_code(401); exit; } $in = json_decode(file_get_contents('php://input'), true); if (!is_free($in['start'])) { echo json_encode(['success' => false, 'error' => "Essa hora está ocupada. Proponha 11:00 ou 14:30 no mesmo dia."]); exit; } book($in['start'], $in['name'], $in['phone']); echo json_encode(['success' => true, 'message' => "Consulta marcada para " . $in['start'] . '.']);
A secção Ferramentas
As ferramentas vivem numa única lista, a secção Ferramentas do painel, e os agentes usam-nas por referência: edite uma ferramenta uma vez e todos os agentes que a usam são atualizados. Uma ferramenta atribuída a um cliente só pode ser usada pelos agentes desse cliente; uma ferramenta sem cliente pode ser usada por todos os seus agentes. No editor do agente escolhe as ferramentas da lista, ou cria uma nova que é adicionada à lista.
Sandbox: ferramentas prontas e código de exemplo
A sandbox é um conjunto real de endpoints de exemplo com dados fictícios, separado para cada conta: calendário, mesas de restaurante, stock, pedidos de contacto, contactos, encomendas. As suas 22 ferramentas já estão na secção Ferramentas: adicione-as a um agente e experimente em minutos, sem alojar nada. Cada ferramenta tem um guia com os parâmetros, um pedido e uma resposta de exemplo e o código do endpoint que lhe responde, que pode descarregar e usar como ponto de partida para o seu.
Descarregar todo o código (zip)
Cada conta tem o seu próprio ambiente de teste com os mesmos dados iniciais: três serviços reserváveis com uma marcação já ocupada, oito mesas de restaurante com uma reserva, seis produtos, dois contactos e quatro encomendas. É criado automaticamente na primeira vez que adiciona uma ferramenta da sandbox a um agente, ou a partir da secção Ferramentas, onde também o pode repor nos dados iniciais. Um ambiente não usado durante 30 dias é apagado e pode ser criado de novo.
Basta adicionar uma ferramenta da sandbox a um agente: o painel preenche o endereço com a chave do seu ambiente. Para chamar os endpoints você mesmo, envie a chave no cabeçalho Authorization: Bearer ws_… (no painel, o guia de cada ferramenta mostra o comando com a sua chave). As chamadas são GET, com os argumentos na query string, ou POST, com os argumentos em JSON.
Cada resposta tem estado 200 e um corpo JSON: success true com uma mensagem para dizer a quem liga mais os dados, ou success false com um erro escrito como uma frase para o agente, por exemplo quais os horários livres em alternativa. Os dias são yyyy-MM-dd, as horas HH:mm, os números de telefone só com dígitos. Abaixo, cada área com o que é, onde é usada e todas as suas chamadas, cada uma com um pedido de exemplo e a resposta real.
Clientes
Um cliente é um cliente seu. Atribua agentes a um cliente no editor do agente; o cliente recebe um convite por email e pode iniciar sessão (palavra-passe, Google ou Apple) para ver os seus agentes, chamadas e consumo mensal ao preço que definiu. O cliente paga-lhe a si: a SwissAI só mostra os valores.
Convidar, editar, remover
Criar um cliente no painel envia o convite. Removê-lo desatribui os agentes e revoga o acesso do cliente.
Atribuir um agente
No editor do agente escolha o cliente que vê as suas chamadas; Uso pessoal mantém o agente só para si.
Telefonia
Um agente atende as chamadas que chegam à sua URI SIP na nossa central. Há três formas de levar uma chamada até lá, todas configuradas no menu Telefonia do painel: um PBX externo autorizado por endereço IP, um número de telefone e um cliente WebRTC num site. A partir do painel também pode ligar a qualquer agente a partir do browser para o testar; as chamadas de teste contam como minutos como qualquer outra.
URI SIP e PBX externos
Cada agente tem uma URI SIP, mostrada na lista de agentes, como sip:ag_7f3a2b91@pbx.swissai.dev. O seu PBX envia a chamada para esse endereço: a parte antes do @ escolhe o agente.
As chamadas só são aceites de PBX que tenha declarado. Em Telefonia › PBX externos adicione o PBX com um nome, o cliente a que pertence (ou Uso pessoal) e o endereço IP de onde vêm as chamadas. Não há utilizador nem palavra-passe: o PBX é reconhecido pelo seu endereço IP e pode chamar todos os agentes desse cliente.
; extensions.conf: a extensão 200 chama o agente
exten => 200,1,Dial(SIP/ag_7f3a2b91@pbx.swissai.dev,60)
same => n,Hangup()
; pjsip.conf: a central SwissAI como endpoint de saída, sem autenticação [swissai] type=endpoint context=from-swissai disallow=all allow=alaw,ulaw aors=swissai [swissai] type=aor contact=sip:pbx.swissai.dev:5060 ; extensions.conf: a extensão 200 chama o agente exten => 200,1,Dial(PJSIP/ag_7f3a2b91@swissai,60) same => n,Hangup()
<!-- dialplan: a extensão 200 chama o agente -->
<extension name="swissai_agent">
<condition field="destination_number" expression="^200$">
<action application="bridge" data="sofia/external/ag_7f3a2b91@pbx.swissai.dev"/>
</condition>
</extension>
# kamailio.cfg / opensips.cfg, na request route: as chamadas para 200 vão para o agente if ($rU == "200") { $ru = "sip:ag_7f3a2b91@pbx.swissai.dev:5060"; t_relay(); exit; }
As chamadas de um endereço que não declarou são rejeitadas, e um endereço que insiste é bloqueado durante algum tempo. Se o seu PBX mudar de endereço IP, atualize-o no painel antes de enviar chamadas.
Números
Um número leva chamadas telefónicas normais a um agente. Em Telefonia › Números cada número mostra a conta SIP usada para o registar (utilizador, palavra-passe, host, porta) e o agente que atende.
- Um número que já tem: com Novo número introduza o número e a conta SIP que o seu operador lhe deu: host, porta, utilizador e palavra-passe. Registamo-nos no seu operador e as chamadas para esse número chegam ao agente que escolher.
- Um número novo: com Pedir número escolha para quem é o número (um cliente ou Uso pessoal), o prefixo que pretende, e anexe um documento de identificação, mais a certidão do registo comercial quando o número é para uma empresa. O prefixo deve corresponder ao local de residência do requerente. O pedido fica na lista como à espera do fornecedor; assim que o número é atribuído passa a ativo e só escolhe o agente.
Clientes WebRTC (clique para ligar)
Um cliente WebRTC é um botão de chamada num site: o visitante fala com o agente a partir do browser, sem telefone. Crie-o em Telefonia › Clientes WebRTC: escolha o agente e indique os domínios onde o botão é usado (adicione localhost para testar na sua máquina). Recebe o nome do cliente (wc_…) e uma chave secreta (wk_…), mostrada só uma vez.
A chave secreta nunca deve chegar ao browser. A página pede ao seu backend, o seu backend pede-nos uma palavra-passe temporária com a chave e entrega-a à página. A palavra-passe temporária é válida durante 60 segundos e para uma chamada.
curl -X POST https://developers.swissai.dev/api/webrtc/token \ -H "X-Api-Key: wk_your_secret_key" \ -H "Content-Type: application/json" \ -d '{"client": "wc_3f9a1c2b7d4e", "identity": "visitor-1842"}'
// 200 { "wss": "wss://pbx.swissai.dev:8089", "domain": "pbx.swissai.dev", "user": "w4c1f09ab37de5521", "pass": "9b2e61f0c4a87d35e1f2a6b90c7d4e88", "target": "sip:ag_7f3a2b91@pbx.swissai.dev", "expires_in": 60 }
| Campo | Descrição |
|---|---|
| client | Obrigatório. O nome do cliente mostrado no painel. |
| identity | Opcional. A sua referência para o visitante, por exemplo um id de utilizador. |
| 401 | Cliente desconhecido ou chave errada. |
| 503 | As chamadas do browser não estão disponíveis para este cliente, por exemplo porque o seu agente foi apagado. |
// o seu backend: a chave secreta fica aqui app.post('/call-token', async (req, res) => { const r = await fetch('https://developers.swissai.dev/api/webrtc/token', { method: 'POST', headers: { 'X-Api-Key': process.env.SWISSAI_CLIENT_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ client: 'wc_3f9a1c2b7d4e' }) }); res.status(r.status).json(await r.json()); });
<?php // call-token.php: a chave secreta fica no seu backend $ch = curl_init('https://developers.swissai.dev/api/webrtc/token'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('SWISSAI_CLIENT_KEY'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['client' => 'wc_3f9a1c2b7d4e']), ]); $body = curl_exec($ch); http_response_code(curl_getinfo($ch, CURLINFO_HTTP_CODE)); header('Content-Type: application/json'); echo $body;
Carregue o nosso script e passe-lhe a resposta tal como está. onState recebe connecting, calling, connected e idle; onError recebe o motivo quando a chamada não pode começar.
<button id="call">Ligue-nos</button> <button id="hangup" hidden>Desligar</button> <p id="status"></p> <script src="https://developers.swissai.dev/js/call.js"></script> <script> const call = document.getElementById('call'), hangup = document.getElementById('hangup'), status = document.getElementById('status'); call.onclick = async () => { const params = await (await fetch('/call-token', { method: 'POST' })).json(); // o seu backend, passo 1 SwissCall.start(params, { onState: s => { status.textContent = s; call.hidden = s !== 'idle'; hangup.hidden = s === 'idle'; }, onError: m => { status.textContent = m; } }); }; hangup.onclick = () => SwissCall.hangup(); </script>
GetCalls: chamadas e o que custam ao seu cliente
Com o nome e a chave secreta de um cliente WebRTC o seu backend pode ler as chamadas do cliente final a que esse cliente pertence (todos os agentes desse cliente) e o que cada chamada lhe custa à tabela de preços definida em Clientes.
curl "https://developers.swissai.dev/api/calls?client=wc_3f9a1c2b7d4e&month=2026-10" \ -H "X-Api-Key: wk_your_secret_key"
// 200 { "month": "2026-10", "customer": { "customer_id": 7, "name": "Dental Practice Keller" }, "currency": "CHF", "rate": 0.25, "free_minutes": 100, "calls": [ { "call_ref": "c-91f3a2", "agent_id": "ag_7f3a2b91", "agent_name": "Anna", "source": "webrtc", "from": "", "started": "2026-10-06 10:41:03", "seconds": 84, "outcome": "appointment", "tool_calls": 2, "cost": 0.35 } ], "total": { "minutes": 312.4, "calls": 148, "billable_minutes": 213, "amount": 53.25 } }
| Campo | Descrição |
|---|---|
| client | Obrigatório. O nome do cliente mostrado no painel. |
| month | Opcional. yyyy-MM; o mês atual quando omitido. Até 500 chamadas, da mais recente. |
| cost | A duração da chamada ao preço por minuto do cliente, antes dos minutos incluídos. |
| total | O mês como o cliente o vê no seu portal: minutos incluídos descontados, segundos arredondados ao minuto no total mensal. |
| customer | null quando o agente do cliente WebRTC é de uso pessoal: as chamadas são as dos seus agentes sem cliente, ao preço da plataforma. |
Pode experimentar as duas chamadas, com o código para copiar, na Área de teste do painel.
Chat: mensagens de texto para o agente
O mesmo agente que atende o telefone pode responder por escrito: no seu site, na sua app ou num canal de mensagens que gere. O seu backend envia o que o utilizador escreveu e recebe a resposta em texto; ao responder, o agente pode chamar as suas ferramentas como faz ao telefone.
A chamada faz-se com o nome e a chave secreta de um cliente WebRTC, que escolhem o agente, e tem de partir do seu backend: a chave nunca deve chegar ao browser. Não guarda estado: envie sempre as últimas trocas em history.
curl -X POST https://developers.swissai.dev/api/messages \ -H "X-Api-Key: wk_your_secret_key" \ -H "Content-Type: application/json" \ -d '{ "client": "wc_3f9a1c2b7d4e", "user": "visitor-1842", "name": "Laura Meier", "message": "Quinta às 10 ainda está livre?", "history": [ { "direction": "in", "text": "Bom dia, queria marcar uma consulta de controlo." }, { "direction": "out", "text": "Com certeza. Que dia lhe dava jeito?" } ] }'
// 200 { "reply": "Sim, quinta às 10:00 está livre. Quer que marque?" }
| Campo | Descrição |
|---|---|
| client | Obrigatório. O nome do cliente mostrado no painel. |
| message | Obrigatório. O texto escrito pelo utilizador, até 4000 caracteres. |
| user | Opcional. A sua referência para quem escreve, por exemplo um id de utilizador ou um número de telefone. |
| name | Opcional. O nome de quem escreve, quando o conhece. |
| history | Opcional. As últimas trocas, da mais antiga para a mais recente, até 20: direction é in para o utilizador e out para o agente, text é a mensagem. |
| reply | A resposta do agente para mostrar ao utilizador; vazia quando o agente não tem nada a dizer. |
| 401 | Cliente desconhecido ou chave errada. |
| 402 | O teste ou o plano não estão ativos, ou a carteira não tem crédito. |
| 502 | O agente não conseguiu responder: tente de novo. |
Pode conversar com qualquer agente em Telefonia › Chat, e experimentar esta chamada, com o código para copiar, na Área de teste.
WhatsApp Business
Um agente pode responder no WhatsApp de duas formas. Ou liga o número a partir do painel e a SwissAI recebe as mensagens e responde, ou mantém a sua própria app Meta e o seu webhook no seu site e envia cada mensagem ao agente com a chamada de chat acima.
A. Ligar o número a partir do painel
Telefonia › WhatsApp › Ligar com o WhatsApp. Abre-se uma janela da Meta: inicia sessão com a conta Facebook da sua empresa, escolhe o portefólio empresarial (ou cria um) e o número; a ligação completa-se sozinha. Escolhe o agente que responde e pode mudá-lo depois na lista.
Duas formas de usar o número: dedicado ao agente (o número deixa de ser usado na app do telemóvel), ou manter o WhatsApp Business no telemóvel, onde o agente responde e você continua a usar a app. A segunda precisa da app WhatsApp Business atualizada e de um número em uso real nela há pelo menos uma semana, caso contrário a Meta rejeita-o.
Cada mensagem recebida vai para o agente juntamente com as últimas 20 trocas dessa conversa, e a resposta é enviada de volta no WhatsApp; as mensagens de voz são transcritas. Cada troca aparece nos Registos. O agente só responde enquanto o teste ou um plano estão ativos e a carteira tem crédito; as mensagens não consomem crédito. A Meta fatura as conversas às suas próprias tarifas e precisa de um método de pagamento na sua conta empresarial antes de o agente poder responder.
Desligue na mesma página: o número é libertado da Cloud API e o histórico das conversas no portal é apagado.
B. A sua própria app Meta e o seu webhook
Se já usa a WhatsApp Cloud API com a sua própria app Meta, mantenha-a. O seu webhook recebe a mensagem, envia-a ao agente com a chamada de chat (user é o número do remetente, name o nome do perfil, history as últimas trocas, que guarda você mesmo: a chamada não tem estado) e envia a resposta com a Graph API. O agente é o do cliente WebRTC que indicar; a sua chave secreta fica no seu servidor.
// o seu backend: webhook Meta → agente SwissAI → Graph API app.post('/webhook', express.json(), async (req, res) => { res.sendStatus(200); // responda à Meta de imediato, depois trabalhe for (const entry of req.body.entry || []) for (const change of entry.changes || []) { const v = change.value, m = (v.messages || [])[0]; if (!m || m.type !== 'text') continue; const name = ((v.contacts || [])[0]?.profile || {}).name || ''; const r = await fetch('https://developers.swissai.dev/api/messages', { method: 'POST', headers: { 'X-Api-Key': process.env.SWISSAI_CLIENT_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ client: 'wc_3f9a1c2b7d4e', user: '+' + m.from, name: name, message: m.text.body, history: lastExchanges(m.from) // as últimas trocas que guardou, da mais antiga }) }).then(r => r.json()); if (!r.reply) continue; // a resposta volta pela Graph API, a partir do seu número await fetch('https://graph.facebook.com/v25.0/' + v.metadata.phone_number_id + '/messages', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.WA_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ messaging_product: 'whatsapp', to: m.from, type: 'text', text: { body: r.reply } }) }); } });
A verificação do webhook (GET com hub.verify_token e hub.challenge), a verificação da assinatura de cada evento (X-Hub-Signature-256) e a subscrição do campo messages são as da WhatsApp Cloud API: veja a documentação da Meta. Guarde as últimas trocas por remetente para as poder enviar em history: o agente só sabe o que lhe enviar.
As duas formas usam o mesmo agente, com o seu scope e as suas ferramentas: escolha A se não quiser gerir um webhook, B se o WhatsApp já faz parte da sua plataforma.
Faturação
Cada conta começa com um teste gratuito de 30 dias que inclui 100 minutos; não é preciso cartão. Depois do teste escolhe um plano em Faturação. A mensalidade é carregada na sua carteira como crédito do plano, e cada chamada é descontada dela ao segundo à tarifa por minuto do plano. O crédito do plano repõe-se em cada renovação. Quando se esgota pode carregar a carteira: o crédito de carregamento nunca expira e é usado depois do crédito do plano. Os números de telefone atribuídos a pedido são debitados na carteira todos os meses. Os agentes só atendem enquanto o teste ou um plano estão ativos e a carteira tem crédito.