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"]
  }
}
CampoParaDescrição
namemodeloNome da função, letras e underscores.
descriptionmodeloO que faz e quando a chamar. Escreva-o para o modelo.
schemamodeloEsquema JSON dos argumentos.
response_schemamodeloOpcional. Forma da sua resposta, para o modelo a poder ler.
urlruntimeO seu endpoint, HTTPS.
methodruntimeGET (argumentos na query string) ou POST (argumentos como corpo JSON).
content_typeruntimeNormalmente application/json.
headersruntimeOpcional. Cabeçalhos enviados em cada chamada, ex. Authorization; os valores podem conter {{parâmetros}}.
bodyruntimeOpcional. 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:

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.

Pedido enviado pela plataforma
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" }
Node.js (Express)
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
<?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.

Asterisk (chan_sip)
; extensions.conf: a extensão 200 chama o agente
exten => 200,1,Dial(SIP/ag_7f3a2b91@pbx.swissai.dev,60)
 same => n,Hangup()
Asterisk (PJSIP)
; 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()
FreeSWITCH
<!-- 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 / OpenSIPS
# 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.

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.

1O seu backend pede a palavra-passe temporária
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
}
CampoDescrição
clientObrigatório. O nome do cliente mostrado no painel.
identityOpcional. A sua referência para o visitante, por exemplo um id de utilizador.
401Cliente desconhecido ou chave errada.
503As chamadas do browser não estão disponíveis para este cliente, por exemplo porque o seu agente foi apagado.
Node.js (Express)
// 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
<?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;
2A página inicia a chamada

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 }
}
CampoDescrição
clientObrigatório. O nome do cliente mostrado no painel.
monthOpcional. yyyy-MM; o mês atual quando omitido. Até 500 chamadas, da mais recente.
costA duração da chamada ao preço por minuto do cliente, antes dos minutos incluídos.
totalO mês como o cliente o vê no seu portal: minutos incluídos descontados, segundos arredondados ao minuto no total mensal.
customernull 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?" }
CampoDescrição
clientObrigatório. O nome do cliente mostrado no painel.
messageObrigatório. O texto escrito pelo utilizador, até 4000 caracteres.
userOpcional. A sua referência para quem escreve, por exemplo um id de utilizador ou um número de telefone.
nameOpcional. O nome de quem escreve, quando o conhece.
historyOpcional. 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.
replyA resposta do agente para mostrar ao utilizador; vazia quando o agente não tem nada a dizer.
401Cliente desconhecido ou chave errada.
402O teste ou o plano não estão ativos, ou a carteira não tem crédito.
502O 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.