Guida
SwissAI ti dà agenti vocali che rispondono alle telefonate. Li crei dal pannello con un prompt, una lingua e tool opzionali; ogni agente ha un SIP URI. Instrada le chiamate a quell'URI e l'agente risponde, parla con chi chiama e chiama i tuoi endpoint HTTP quando gli servono dati o deve agire.
Tutto si configura dal pannello: agenti, tool, i tuoi clienti e la fatturazione.
Agenti
Gli agenti si creano e si modificano dalla dashboard: nome, lingua, scope (il prompt), una knowledge base facoltativa, i modelli e i tool. Ogni agente riceve un id e un SIP URI e può essere assegnato a un tuo cliente.
Esempio di scope
Scrivi lo scope come daresti le consegne a un nuovo collega: chi è l'agente, cosa può fare, cosa non deve mai fare e come si chiude la chiamata.
Sei Anna, l'assistente dello Studio Dentistico Keller di Zurigo. Rispondi al telefono quando la segreteria è occupata o chiusa. Cosa fai: - fissi, sposti o annulli gli appuntamenti, usando i tool; - rispondi alle domande su orari, indirizzo e prezzi (vedi la knowledge base). Regole: - parla in modo breve e cortese, una domanda alla volta; - prima di fissare, ripeti giorno, ora e nome e aspetta un sì chiaro; - non dare mai consigli medici: per dolore o urgenze proponi il primo appuntamento libero e dai il numero delle emergenze; - se non puoi aiutare, prendi nome e numero di telefono e di' che lo studio richiamerà. Chiudi la chiamata ripetendo quello che è stato concordato.
Tool
Definizione di un tool
Un tool è un endpoint HTTP dalla tua parte, descritto al modello con uno schema JSON. Il modello decide quando chiamarlo e con quali argomenti; la piattaforma esegue la richiesta HTTP.
{
"name": "book_appointment",
"description": "Fissa un appuntamento. Chiamalo solo dopo che chi chiama ha confermato giorno, ora 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": "Codice dello studio: sempre 'zurich'" },
"start": { "type": "string", "description": "Inizio, yyyy-MM-dd HH:mm, es. 2026-10-06 10:30" },
"name": { "type": "string", "description": "Nome e cognome di chi chiama" },
"phone": { "type": "string", "description": "Numero di telefono di chi chiama, solo cifre" }
},
"required": ["clinic", "start", "name", "phone"]
}
}
| Campo | Per | Descrizione |
|---|---|---|
| name | modello | Nome della funzione, lettere e underscore. |
| description | modello | Cosa fa e quando chiamarla. Scrivila per il modello. |
| schema | modello | Schema JSON degli argomenti. |
| response_schema | modello | Opzionale. Forma della tua risposta, così il modello la sa leggere. |
| url | runtime | Il tuo endpoint, HTTPS. |
| method | runtime | GET (argomenti in query string) o POST (argomenti nel corpo JSON). |
| content_type | runtime | Di norma application/json. |
| headers | runtime | Opzionale. Header mandati a ogni chiamata, es. Authorization; i valori possono contenere {{parametri}}. |
| body | runtime | Opzionale. Modello del body con {{parametri}}. Vuoto: i parametri non usati in url o header vanno in query string (GET, DELETE) o nel body JSON. |
Parametri e placeholder
Scrivi {{nome}} nell'url, nei valori degli header o nel body. Ogni placeholder va dichiarato in schema.properties con una descrizione: il modello lo riempie dalla conversazione e il runtime lo sostituisce (codificato nell'url, con escape in un body JSON). La dashboard costruisce lo schema dai placeholder e fa verificare il tool a Claude prima di salvarlo.
Come l'agente ti chiama
Ogni richiesta porta l'header token con il tool_token dell'agente. Rispondi 200 con un corpo JSON:
- Successo: { "success": true, "message": "…" } più i dati. Il messaggio è pensato per essere riferito al chiamante.
- Errore di business: { "success": false, "error": "…" }. Scrivi l'errore come una frase per il modello: cosa chiedere o proporre in alternativa.
- 401 / 500: l'agente si scusa e propone di essere richiamati. Rispondi in pochi secondi: il chiamante è in linea.
Il tuo endpoint: esempi
Questa è la richiesta che la piattaforma manda per il tool qui sopra, e un endpoint minimo che le risponde. Per prima cosa controlla l'header token: è il tool token che hai impostato sull'agente.
POST /clinics/zurich/appointments HTTP/1.1 Host: api.example.ch Authorization: Bearer sk_… token: 5f1c… # il tool token dell'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. può chiamare solo il tuo agente if (req.get('token') !== process.env.SWISSAI_TOOL_TOKEN) return res.status(401).end(); const { start, name, phone } = req.body; // 2. errore di business: una frase su cui l'agente può agire if (!(await isFree(req.params.clinic, start))) return res.json({ success: false, error: "Quell'orario è occupato. Proponi le 11:00 o le 14:30 dello stesso giorno." }); // 3. successo: il messaggio è quello che l'agente dice a chi chiama await book(req.params.clinic, start, name, phone); res.json({ success: true, message: "Appuntamento fissato per " + 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' => "Quell'orario è occupato. Proponi le 11:00 o le 14:30 dello stesso giorno."]); exit; } book($in['start'], $in['name'], $in['phone']); echo json_encode(['success' => true, 'message' => "Appuntamento fissato per " . $in['start'] . '.']);
La sezione Tool
I tool stanno in un unico elenco, la sezione Tool della dashboard, e gli agenti li usano per riferimento: modifichi un tool una volta e ogni agente che lo usa viene aggiornato. Un tool assegnato a un cliente si può usare solo negli agenti di quel cliente; un tool senza cliente si può usare in tutti i tuoi agenti. Nell'editor dell'agente scegli i tool dall'elenco, oppure ne crei uno nuovo che viene aggiunto all'elenco.
Sandbox: tool pronti e codice d'esempio
La sandbox è un insieme di endpoint d'esempio funzionanti, con dati di prova separati per ogni account: calendario, tavoli, magazzino, richiamate, rubrica, ordini. I suoi 22 tool sono già nella sezione Tool: aggiungili a un agente e provalo in pochi minuti, senza ospitare niente. Ogni tool ha una guida con i parametri, un esempio svolto di richiesta e risposta e il codice dell'endpoint che risponde, che puoi scaricare e usare come base per il tuo.
Ogni account ha il suo ambiente di prova con gli stessi dati iniziali: tre prestazioni prenotabili con un appuntamento già preso, otto tavoli con una prenotazione, sei prodotti, due contatti e quattro ordini. Nasce da solo la prima volta che aggiungi un tool della sandbox a un agente, oppure dalla sezione Tool, dove puoi anche riportarlo ai dati iniziali. Un ambiente non usato per 30 giorni viene cancellato e si può ricreare.
Basta aggiungere un tool della sandbox a un agente: la dashboard completa l'indirizzo con la chiave del tuo ambiente. Per chiamare gli endpoint a mano, manda la chiave nell'header Authorization: Bearer ws_… (nella dashboard, la guida di ogni tool mostra il comando con la tua chiave). Le chiamate sono GET, con gli argomenti nella query string, oppure POST, con gli argomenti in JSON.
Ogni risposta ha stato 200 e un corpo JSON: success true con un messaggio da dire a chi chiama più i dati, oppure success false con un errore scritto come una frase per l'agente, per esempio quali orari sono liberi in alternativa. I giorni sono yyyy-MM-dd, gli orari HH:mm, i telefoni solo cifre. Qui sotto, ogni area con cos'è, dove si usa e tutte le sue chiamate, ognuna con un esempio di richiesta e la risposta vera.
Clienti
Un cliente è un tuo cliente finale. Assegnagli gli agenti dall'editor dell'agente; riceve un invito via email e può accedere (password, Google o Apple) per vedere i suoi agenti, le chiamate e i consumi del mese valorizzati alla tariffa che decidi tu. Il cliente paga te: SwissAI mostra solo i numeri.
Invitare, modificare, rimuovere
Creando un cliente nel pannello parte l'invito. Rimuovendolo gli agenti tornano a te e il suo accesso viene revocato.
Assegnare un agente
Nell'editor dell'agente scegli il cliente che ne vede le chiamate; Uso personale lo lascia solo a te.
Telefonia
Un agente risponde alle chiamate che arrivano al suo SIP URI sulla nostra centrale. Ci sono tre modi per portarci una chiamata, tutti configurabili dal menu Telefonia della dashboard: un PBX esterno abilitato per indirizzo IP, una numerazione e un client WebRTC su un sito web. Dalla dashboard puoi anche chiamare qualsiasi agente dal browser per provarlo; le prove consumano minuti come ogni altra chiamata.
SIP URI e PBX esterni
Ogni agente ha un SIP URI, visibile nell'elenco degli agenti, del tipo sip:ag_7f3a2b91@pbx.swissai.dev. Il tuo centralino manda la chiamata a quell'indirizzo: la parte prima della @ sceglie l'agente.
Le chiamate sono accettate solo dai centralini che hai dichiarato. In Telefonia › PBX esterni aggiungi il centralino con un nome, il cliente a cui appartiene (o Uso personale) e l'indirizzo IP da cui arrivano le sue chiamate. Non ci sono username e password: il centralino è riconosciuto dal suo indirizzo IP e può chiamare tutti gli agenti di quel cliente.
; extensions.conf: l'interno 200 chiama l'agente
exten => 200,1,Dial(SIP/ag_7f3a2b91@pbx.swissai.dev,60)
same => n,Hangup()
; pjsip.conf: la centrale SwissAI come endpoint in uscita, senza autenticazione [swissai] type=endpoint context=from-swissai disallow=all allow=alaw,ulaw aors=swissai [swissai] type=aor contact=sip:pbx.swissai.dev:5060 ; extensions.conf: l'interno 200 chiama l'agente exten => 200,1,Dial(PJSIP/ag_7f3a2b91@swissai,60) same => n,Hangup()
<!-- dialplan: l'interno 200 chiama l'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, nella request route: le chiamate al 200 vanno all'agente if ($rU == "200") { $ru = "sip:ag_7f3a2b91@pbx.swissai.dev:5060"; t_relay(); exit; }
Le chiamate da un indirizzo non dichiarato vengono rifiutate, e un indirizzo che insiste viene bloccato per un po'. Se il tuo centralino cambia indirizzo IP, aggiornalo nella dashboard prima di mandare chiamate.
Numerazioni
Una numerazione porta a un agente le normali telefonate. In Telefonia › Numerazioni ogni numero mostra l'utenza SIP usata per registrarlo (username, password, host, porta) e l'agente che risponde.
- Un numero che hai già: con Nuova numerazione inserisci il numero e l'utenza SIP che ti ha dato il tuo operatore: host, porta, username e password. Ci registriamo noi presso il tuo operatore e le chiamate a quel numero arrivano all'agente che scegli.
- Un numero nuovo: con Richiedi numerazione scegli per chi è il numero (un cliente o Uso personale), il prefisso che vuoi, e allega un documento d'identità, più la visura camerale quando il numero è per un'azienda. Il prefisso deve coincidere con la residenza del richiedente. La richiesta resta nell'elenco come in attesa del fornitore; quando il numero viene assegnato diventa attiva e devi solo scegliere l'agente.
Client WebRTC (click to call)
Un client WebRTC è un pulsante di chiamata su un sito web: il visitatore parla con l'agente dal browser, senza telefono. Crealo in Telefonia › Client WebRTC: scegli l'agente ed elenca i domini su cui usi il pulsante (aggiungi localhost per provare sul tuo computer). Ricevi il nome del client (wc_…) e una chiave segreta (wk_…), mostrata una sola volta.
La chiave segreta non deve mai arrivare al browser. La pagina chiede al tuo backend, il tuo backend chiede a noi una password temporanea usando la chiave, e la passa alla pagina. La password temporanea vale 60 secondi e una sola chiamata.
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 | Descrizione |
|---|---|
| client | Obbligatorio. Il nome del client mostrato nella dashboard. |
| identity | Opzionale. Il tuo riferimento per il visitatore, per esempio un id utente. |
| 401 | Client sconosciuto o chiave sbagliata. |
| 503 | Le chiamate dal browser non sono disponibili per questo client, per esempio perché il suo agente è stato eliminato. |
// il tuo backend: la chiave segreta resta qui 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: la chiave segreta resta sul tuo 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;
Carica il nostro script e passagli la risposta così com'è. onState riceve connecting, calling, connected e idle; onError riceve il motivo quando la chiamata non può partire.
<button id="call">Chiamaci</button> <button id="hangup" hidden>Riaggancia</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(); // il tuo 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: le chiamate e quanto costano al tuo cliente
Con il nome e la chiave segreta di un client WebRTC il tuo backend può leggere le chiamate del cliente a cui appartiene quel client (tutti gli agenti di quel cliente) e quanto costa ogni chiamata al cliente, al listino che hai impostato in Clienti.
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 | Descrizione |
|---|---|
| client | Obbligatorio. Il nome del client mostrato nella dashboard. |
| month | Opzionale. yyyy-MM; il mese corrente se manca. Fino a 500 chiamate, dalla più recente. |
| cost | La durata della chiamata al prezzo al minuto del cliente, prima dei minuti inclusi. |
| total | Il mese come lo vede il cliente nel suo portale: minuti inclusi scalati, secondi arrotondati al minuto sul totale del mese. |
| customer | null quando l'agente del client è a uso personale: le chiamate sono quelle dei tuoi agenti senza cliente, al prezzo della piattaforma. |
Puoi provare entrambe le chiamate, con il codice da copiare, dall'Area test della dashboard.
Chat: messaggi di testo all'agente
Lo stesso agente che risponde al telefono può rispondere per iscritto: sul tuo sito, nella tua app o su un canale di messaggistica che gestisci tu. Il tuo backend manda quello che ha scritto l'utente e riceve la risposta come testo; mentre risponde, l'agente può chiamare i suoi tool come fa al telefono.
La chiamata si fa con il nome e la chiave segreta di un client WebRTC, che scelgono l'agente, e deve partire dal tuo backend: la chiave non deve mai arrivare al browser. Non conserva lo stato: rimanda ogni volta gli ultimi scambi in 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": "Giovedì alle 10 è ancora libero?", "history": [ { "direction": "in", "text": "Buongiorno, vorrei prenotare una visita di controllo." }, { "direction": "out", "text": "Certo. Che giorno le andrebbe bene?" } ] }'
// 200 { "reply": "Sì, giovedì alle 10:00 è libero. Glielo prenoto?" }
| Campo | Descrizione |
|---|---|
| client | Obbligatorio. Il nome del client mostrato nella dashboard. |
| message | Obbligatorio. Il testo scritto dall'utente, fino a 4000 caratteri. |
| user | Opzionale. Il tuo riferimento per chi scrive, per esempio un id utente o un numero di telefono. |
| name | Opzionale. Il nome di chi scrive, quando lo conosci. |
| history | Opzionale. Gli ultimi scambi, dal più vecchio, fino a 20: direction è in per l'utente e out per l'agente, text è il messaggio. |
| reply | La risposta dell'agente da mostrare all'utente; vuota quando l'agente non ha niente da dire. |
| 401 | Client sconosciuto o chiave sbagliata. |
| 402 | La prova o il piano non sono attivi, oppure il wallet non ha credito. |
| 502 | L'agente non ha potuto rispondere: riprova. |
Puoi chattare con qualunque agente da Telefonia › Chat, e provare questa chiamata, col codice da copiare, dall'Area test.
WhatsApp Business
Un agente può rispondere su WhatsApp in due modi. O colleghi il numero dalla dashboard e SwissAI riceve i messaggi e risponde, oppure tieni la tua app Meta e il tuo webhook sul tuo sito e mandi ogni messaggio all'agente con la chiamata della chat qui sopra.
A. Collegare il numero dalla dashboard
Telefonia › WhatsApp › Collega con WhatsApp. Si apre una finestra di Meta: accedi con l'account Facebook della tua azienda, scegli il portfolio aziendale (o ne crei uno) e il numero; il collegamento si completa da solo. Scegli l'agente che risponde e puoi cambiarlo dopo, dall'elenco.
Due modi di usare il numero: dedicato all'agente (il numero non si usa più dall'app del telefono), oppure tieni WhatsApp Business sul telefono: risponde l'agente e tu continui a usare l'app. Il secondo richiede l'app WhatsApp Business aggiornata e un numero in uso reale sull'app da almeno una settimana, altrimenti Meta lo rifiuta.
Ogni messaggio in arrivo va all'agente insieme agli ultimi 20 scambi di quella conversazione, e la risposta torna su WhatsApp; i vocali vengono trascritti. Ogni scambio compare nei Log. L'agente risponde solo con la prova o un piano in corso e credito nel wallet; i messaggi non consumano credito. Meta fattura le conversazioni alle sue tariffe e richiede un metodo di pagamento sul tuo account aziendale prima che l'agente possa rispondere.
Scollega dalla stessa pagina: il numero viene liberato dalla Cloud API e lo storico delle conversazioni sul portale viene cancellato.
B. La tua app Meta e il tuo webhook
Se usi già la WhatsApp Cloud API con una tua app Meta, tienila. Il tuo webhook riceve il messaggio, lo manda all'agente con la chiamata della chat (user è il numero del mittente, name il nome del profilo, history gli ultimi scambi, che conservi tu: la chiamata non ha stato) e manda la risposta con la Graph API. L'agente è quello del client WebRTC che indichi; la sua chiave segreta resta sul tuo server.
// il tuo backend: webhook Meta → agente SwissAI → Graph API app.post('/webhook', express.json(), async (req, res) => { res.sendStatus(200); // rispondi subito a Meta, poi lavora 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) // gli ultimi scambi che hai conservato, dal più vecchio }) }).then(r => r.json()); if (!r.reply) continue; // la risposta torna con la Graph API, dal tuo numero 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 } }) }); } });
La verifica del webhook (GET con hub.verify_token e hub.challenge), il controllo della firma di ogni evento (X-Hub-Signature-256) e l'iscrizione al campo messages sono quelli della WhatsApp Cloud API: vedi la documentazione di Meta. Conserva gli ultimi scambi per mittente, così li mandi in history: l'agente sa solo quello che gli mandi.
Le due strade usano lo stesso agente, con il suo scope e i suoi tool: scegli la A se non vuoi gestire un webhook, la B se WhatsApp fa già parte della tua piattaforma.
Fatturazione
Ogni account parte con una prova gratuita di 30 giorni che include 100 minuti; non serve la carta. Finita la prova scegli un piano in Fatturazione. Il canone mensile viene caricato nel wallet come credito del piano e ogni chiamata lo scala al secondo, alla tariffa al minuto del piano. Il credito del piano si azzera a ogni rinnovo. Quando finisce puoi ricaricare il wallet: il credito ricaricato non scade e si usa dopo quello del piano. Le numerazioni rilasciate su richiesta si pagano dal wallet ogni mese. Gli agenti rispondono solo con la prova o un piano in corso e credito nel wallet.